/* eslint-disable prefer-spread */
/* eslint-disable @typescript-eslint/camelcase */
/* eslint-disable no-control-regex */
/* eslint-disable no-redeclare */
/* eslint-disable max-len */
/* eslint-disable no-var */
import mxgraph from "src/pages/mxgraph/graph/index";
import Base64 from "src/pages/mxgraph/graph/Base64";
import Editor from "src/pages/mxgraph/graph/Editor";
import pako from "pako";
import { generateUUID } from "ant-design-vue/lib/vc-select/util";
import { InteractionHelper, UMLDIHelper } from "src/api/sysml-imports";
import canvasUtil from "src/components/Graph/classes/canvasUtil";

const {
  mxGraph,
  mxVertexHandler,
  mxConstants,
  mxCellState,
  mxCellEditor,
  mxGraphHandler,
  mxEvent,
  mxShape,
  mxConnectionConstraint,
  mxPoint,
  mxEventObject,
  mxCodec,
  mxUtils,
  mxImageExport,
  mxResources,
  mxClient,
  mxPopupMenu,
  mxStencilRegistry,
  mxRubberband,
  mxText,
  mxGraphView,
  mxSvgCanvas2D,
  mxRectangle,
  mxCellHighlight,
  mxImage,
  mxCellRenderer,
  mxConnector,
  mxStencil,
  mxGuide,
  mxConnectionHandler,
  mxConstraintHandler,
  mxOutline,
  mxPanningHandler,
  mxDragSource,
  mxLayoutManager,
  mxPolyline,
  mxImageShape,
  mxDictionary,
  mxStackLayout,
  mxEllipse,
  mxElbowEdgeHandler,
  mxPopupMenuHandler, //// edge浏览器鼠标移动报错
  mxCell,
  mxSelectionCellsHandler,
  mxEdgeStyle
} = mxgraph;

//// Changes default colors
/**
 * Measurements Units
 */
mxConstants.POINTS = 1;
mxConstants.MILLIMETERS = 2;
mxConstants.INCHES = 3;
/**
 * This ratio is with page scale 1
 */
mxConstants.PIXELS_PER_MM = 3.937;
mxConstants.PIXELS_PER_INCH = 100;

mxConstants.SHADOW_OPACITY = 0.25;
mxConstants.SHADOWCOLOR = "#000000";
mxConstants.VML_SHADOWCOLOR = "#d0d0d0";
//// 自定义page的大小
mxConstants.PAGE_FORMAT_LETTER_PORTRAIT = new mxRectangle(0, 0, 1600, 980);
mxConstants.PAGE_FORMAT_A4_PORTRAIT = new mxRectangle(0, 0, 1600, 980);
mxGraph.prototype.pageBreakColor = "#c0c0c0";
mxGraph.prototype.pageScale = 1;

//// Letter page format is default in US, Canada and Mexico
(function () {
  try {
    if (navigator != null && navigator.language != null) {
      const lang = navigator.language.toLowerCase();
      mxGraph.prototype.pageFormat =
        lang === "en-us" || lang === "en-ca" || lang === "es-mx"
          ? mxConstants.PAGE_FORMAT_LETTER_PORTRAIT
          : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
    }
  } catch (e) {
    //// ignore
  }
})();

//// Matches label positions of mxGraph 1.x
mxText.prototype.baseSpacingTop = 5;
mxText.prototype.baseSpacingBottom = 1;

//// Keeps edges between relative child cells inside parent
mxGraphModel.prototype.ignoreRelativeEdgeParent = false;

//// Alternative text for unsupported foreignObjects
mxSvgCanvas2D.prototype.foAltText = "[Not supported by viewer]";

//// Hook for custom constraints
mxShape.prototype.getConstraints = function (style, w, h) {
  return null;
};

/**
 * Constructs a new graph instance. Note that the constructor does not take a
 * container because the graph instance is needed for creating the UI, which
 * in turn will create the container for the graph. Hence, the container is
 * assigned later in EditorUi.
 */
/**
 * Defines graph class.
 */
const Graph = function (container, model, renderHint, stylesheet, themes) {
  mxGraph.call(this, container, model, renderHint, stylesheet);

  this.themes = themes || this.defaultThemes;
  this.currentEdgeStyle = mxUtils.clone(this.defaultEdgeStyle);
  this.currentVertexStyle = mxUtils.clone(this.defaultVertexStyle);

  //// Sets the base domain URL and domain path URL for relative links.
  const b = this.baseUrl;
  const p = b.indexOf("////");
  this.domainUrl = "";
  this.domainPathUrl = "";

  if (p > 0) {
    let d = b.indexOf("/", p + 2);

    if (d > 0) {
      this.domainUrl = b.substring(0, d);
    }

    d = b.lastIndexOf("/");

    if (d > 0) {
      this.domainPathUrl = b.substring(0, d + 1);
    }
  }

  //// Adds support for HTML labels via style. Note: Currently, only the Java
  //// backend supports HTML labels but CSS support is limited to the following:
  //// http:////docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
  //// TODO: Wrap should not affect isHtmlLabel output (should be handled later)
  this.isHtmlLabel = function (cell) {
    const state = this.view.getState(cell);
    const style = state != null ? state.style : this.getCellStyle(cell);

    return style != null
      ? style["html"] == "1" || style[mxConstants.STYLE_WHITE_SPACE] == "wrap"
      : false;
  };

  //// Implements a listener for hover and click handling on edges
  if (this.edgeMode) {
    const start = {
      point: null,
      event: null,
      state: null,
      handle: null,
      selected: false
    };

    //// Uses this event to process mouseDown to check the selection state before it is changed
    this.addListener(
      mxEvent.FIRE_MOUSE_EVENT,
      mxUtils.bind(this, function (sender, evt) {
        if (evt.getProperty("eventName") == "mouseDown" && this.isEnabled()) {
          const me = evt.getProperty("event");

          if (
            !mxEvent.isControlDown(me.getEvent()) &&
            !mxEvent.isShiftDown(me.getEvent())
          ) {
            const state = me.getState();

            if (state != null) {
              //// Checks if state was removed in call to stopEditing above
              if (this.model.isEdge(state.cell)) {
                start.point = new mxPoint(me.getGraphX(), me.getGraphY());
                start.selected = this.isCellSelected(state.cell);
                start.state = state;
                start.event = me;

                if (
                  state.text != null &&
                  state.text.boundingBox != null &&
                  mxUtils.contains(
                    state.text.boundingBox,
                    me.getGraphX(),
                    me.getGraphY()
                  )
                ) {
                  start.handle = mxEvent.LABEL_HANDLE;
                } else {
                  const handler = this.selectionCellsHandler.getHandler(
                    state.cell
                  );
                  if (
                    handler != null &&
                    handler.bends != null &&
                    handler.bends.length > 0
                  ) {
                    start.handle = handler.getHandleForEvent(me);
                  }
                }
              }
            }
          }
        }
      })
    );

    const mouseDown = null;

    this.addMouseListener({
      mouseDown: function (sender, me) {
      },
      mouseMove: mxUtils.bind(this, function (sender, me) {
        //// Checks if any other handler is active
        const handlerMap = this.selectionCellsHandler.handlers.map;
        for (const key in handlerMap) {
          if (handlerMap[key].index != null) {
            return;
          }
        }

        if (
          this.isEnabled() &&
          !this.panningHandler.isActive() &&
          !mxEvent.isControlDown(me.getEvent()) &&
          !mxEvent.isShiftDown(me.getEvent()) &&
          !mxEvent.isAltDown(me.getEvent())
        ) {
          const tol = this.tolerance;

          if (
            start.point != null &&
            start.state != null &&
            start.event != null
          ) {
            var state = start.state;

            if (
              Math.abs(start.point.x - me.getGraphX()) > tol ||
              Math.abs(start.point.y - me.getGraphY()) > tol
            ) {
              //// Lazy selection for edges inside groups
              if (!this.isCellSelected(state.cell)) {
                this.setSelectionCell(state.cell);
              }

              const handler = this.selectionCellsHandler.getHandler(state.cell);

              if (
                handler != null &&
                handler.bends != null &&
                handler.bends.length > 0
              ) {
                let handle = handler.getHandleForEvent(start.event);
                const edgeStyle = this.view.getEdgeStyle(state);
                const entity = edgeStyle === mxEdgeStyle.EntityRelation;

                //// Handles special case where label was clicked on unselected edge in which
                //// case the label will be moved regardless of the handle that is returned
                if (!start.selected && start.handle === mxEvent.LABEL_HANDLE) {
                  handle = start.handle;
                }

                if (
                  !entity ||
                  handle === 0 ||
                  handle === handler.bends.length - 1 ||
                  handle === mxEvent.LABEL_HANDLE
                ) {
                  //// Source or target handle or connected for direct handle access or orthogonal line
                  //// with just two points where the central handle is moved regardless of mouse position
                  if (
                    handle === mxEvent.LABEL_HANDLE ||
                    handle === 0 ||
                    state.visibleSourceState != null ||
                    handle === handler.bends.length - 1 ||
                    state.visibleTargetState != null
                  ) {
                    if (!entity && handle !== mxEvent.LABEL_HANDLE) {
                      var pts = state.absolutePoints;

                      //// Default case where handles are at corner points handles
                      //// drag of corner as drag of existing point
                      if (
                        pts != null &&
                        ((edgeStyle == null && handle == null) ||
                          edgeStyle === mxEdgeStyle.OrthConnector)
                      ) {
                        //// Does not use handles if they were not initially visible
                        handle = start.handle;

                        if (handle == null) {
                          var box = new mxRectangle(
                            start.point.x,
                            start.point.y
                          );
                          box.grow(
                            canvasUtil.vertexImg.mainHandle.width / 2
                          );

                          if (mxUtils.contains(box, pts[0].x, pts[0].y)) {
                            //// Moves source terminal handle
                            handle = 0;
                          } else if (
                            mxUtils.contains(
                              box,
                              pts[pts.length - 1].x,
                              pts[pts.length - 1].y
                            )
                          ) {
                            //// Moves target terminal handle
                            handle = handler.bends.length - 1;
                          } else {
                            //// Checks if edge has no bends
                            const nobends =
                              edgeStyle != null &&
                              (pts.length == 2 ||
                                (pts.length == 3 &&
                                  ((Math.round(pts[0].x - pts[1].x) == 0 &&
                                    Math.round(pts[1].x - pts[2].x) == 0) ||
                                    (Math.round(pts[0].y - pts[1].y) == 0 &&
                                      Math.round(pts[1].y - pts[2].y) == 0))));

                            if (nobends) {
                              //// Moves central handle for straight orthogonal edges
                              handle = 2;
                            } else {
                              //// Finds and moves vertical or horizontal segment
                              handle = mxUtils.findNearestSegment(
                                state,
                                start.point.x,
                                start.point.y
                              );

                              //// Converts segment to virtual handle index
                              if (edgeStyle == null) {
                                handle = mxEvent.VIRTUAL_HANDLE - handle;
                              }
                              //// Maps segment to handle
                              else {
                                handle += 1;
                              }
                            }
                          }
                        }
                      }

                      //// Creates a new waypoint and starts moving it
                      if (handle == null) {
                        handle = mxEvent.VIRTUAL_HANDLE;
                      }
                    }

                    handler.start(me.getGraphX(), me.getGraphX(), handle);
                    start.state = null;
                    start.event = null;
                    start.point = null;
                    start.handle = null;
                    start.selected = false;
                    me.consume();

                    //// Removes preview rectangle in graph handler
                    this.graphHandler.reset();
                  }
                } else if (
                  entity &&
                  (state.visibleSourceState != null ||
                    state.visibleTargetState != null)
                ) {
                  //// Disables moves on entity to make it consistent
                  this.graphHandler.reset();
                  me.consume();
                }
              }
            }
          } else {
            //// Updates cursor for unselected edges under the mouse
            var state = me.getState();

            if (state != null) {
              //// Checks if state was removed in call to stopEditing above
              if (this.model.isEdge(state.cell)) {
                let cursor = null;
                var pts = state.absolutePoints;

                if (pts != null) {
                  var box = new mxRectangle(me.getGraphX(), me.getGraphY());
                  box.grow(canvasUtil.vertexImg.mainHandle.width / 2);
                  if (
                    state.text != null &&
                    state.text.boundingBox != null &&
                    mxUtils.contains(
                      state.text.boundingBox,
                      me.getGraphX(),
                      me.getGraphY()
                    )
                  ) {
                    cursor = "move";
                  } else if (
                    mxUtils.contains(box, pts[0].x, pts[0].y) ||
                    mxUtils.contains(
                      box,
                      pts[pts.length - 1].x,
                      pts[pts.length - 1].y
                    )
                  ) {
                    cursor = "pointer";
                  } else if (
                    state.visibleSourceState != null ||
                    state.visibleTargetState != null
                  ) {
                    //// Moving is not allowed for entity relation but still indicate hover state
                    const tmp = this.view.getEdgeStyle(state);
                    cursor = "crosshair";

                    if (
                      tmp != mxEdgeStyle.EntityRelation &&
                      this.isOrthogonal(state)
                    ) {
                      const idx = mxUtils.findNearestSegment(
                        state,
                        me.getGraphX(),
                        me.getGraphY()
                      );

                      if (idx < pts.length - 1 && idx >= 0) {
                        cursor =
                          Math.round(pts[idx].x - pts[idx + 1].x) == 0
                            ? "col-resize"
                            : "row-resize";
                      }
                    }
                  }
                }

                if (cursor != null) {
                  state.setCursor(cursor);
                }
              }
            }
          }
        }
      }),
      mouseUp: mxUtils.bind(this, function (sender, me) {
        start.state = null;
        start.event = null;
        start.point = null;
        start.handle = null;
      })
    });
  }

  //// HTML entities are displayed as plain text in wrapped plain text labels
  this.cellRenderer.getLabelValue = function (state) {
    let result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments);

    if (state.view.graph.isHtmlLabel(state.cell)) {
      if (state.style["html"] != 1) {
        result = mxUtils.htmlEntities(result, false);
      } else {
        result = state.view.graph.sanitizeHtml(result);
      }
    }

    return result;
  };

  //// All code below not available and not needed in embed mode
  if (typeof mxVertexHandler !== "undefined") {

    this.setConnectable(true); //// 开启可以拖拽建立关系
    this.setDropEnabled(true);
    this.setPanning(true); //// 右键移动容器坐标轴
    this.setTooltips(false); //// 关闭悬浮提示
    this.setAllowLoops(true);
    this.allowAutoPanning = true;
    this.resetEdgesOnConnect = false;
    this.constrainChildren = false;
    this.constrainRelativeChildren = true;

    //// Do not scroll after moving cells
    this.graphHandler.scrollOnMove = false;
    this.graphHandler.scaleGrid = true;

    //// Disables cloning of connection sources by default
    this.connectionHandler.setCreateTarget(false); //// 是否创建目标
    this.connectionHandler.insertBeforeSource = true;

    //// Sets the style to be used when an elbow edge is double clicked
    this.alternateEdgeStyle = "vertical";

    if (stylesheet == null) {
      this.loadStylesheet();
    }

    //// Adds page centers to the guides for moving cells
    const graphHandlerGetGuideStates = this.graphHandler.getGuideStates;
    this.graphHandler.getGuideStates = function () {
      let result = graphHandlerGetGuideStates.apply(this, arguments);

      //// Create virtual cell state for page centers
      if (this.graph.pageVisible) {
        const guides = [];

        const pf = this.graph.pageFormat;
        const ps = this.graph.pageScale;
        const pw = pf.width * ps;
        const ph = pf.height * ps;
        const t = this.graph.view.translate;
        const s = this.graph.view.scale;

        const layout = this.graph.getPageLayout();

        for (let i = 0; i < layout.width; i++) {
          guides.push(
            new mxRectangle(
              ((layout.x + i) * pw + t.x) * s,
              (layout.y * ph + t.y) * s,
              pw * s,
              ph * s
            )
          );
        }

        for (let j = 0; j < layout.height; j++) {
          guides.push(
            new mxRectangle(
              (layout.x * pw + t.x) * s,
              ((layout.y + j) * ph + t.y) * s,
              pw * s,
              ph * s
            )
          );
        }

        //// Page center guides have predence over normal guides
        result = guides.concat(result);
      }

      return result;
    };

    //// Overrides zIndex for dragElement
    mxDragSource.prototype.dragElementZIndex = mxPopupMenu.prototype.zIndex;

    //// Overrides color for virtual guides for page centers
    mxGuide.prototype.getGuideColor = function (state, horizontal) {
      return state.cell == null
        ? "#ffa500" /* orange */
        : mxConstants.GUIDE_COLOR;
    };

    this.rubberband = new mxRubberband(this);

    //// this.getRubberband = function () {
    ////   return rubberband;
    //// };

    //// Timer-based activation of outline connect in connection handler
    let startTime = new Date().getTime();
    let timeOnTarget = 0;

    mxConnectionHandler.prototype.mouseMove = function(sender, me) {
      if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown)) {
        //// Handles special case when handler is disabled during highlight
        if (!this.isEnabled() && this.currentState != null) {
          this.destroyIcons();
          this.currentState = null;
        }

        var view = this.graph.getView();
        var scale = view.scale;
        var tr = view.translate;
        var point = new mxPoint(me.getGraphX(), me.getGraphY());
        this.error = null;

        if (this.graph.isGridEnabledEvent(me.getEvent())) {
          point = new mxPoint((this.graph.snap(point.x / scale - tr.x) + tr.x) * scale,
            (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale);
        }

        this.snapToPreview(me, point);
        this.currentPoint = point;

        if ((this.first != null || (this.isEnabled() && this.graph.isEnabled())) &&
          (this.shape != null || this.first == null ||
          Math.abs(me.getGraphX() - this.first.x) > this.graph.tolerance ||
          Math.abs(me.getGraphY() - this.first.y) > this.graph.tolerance)) {
          this.updateCurrentState(me, point);
        }

        if (this.first != null) {
          var constraint = null;
          var current = point;

          //// Uses the current point from the constraint handler if available
          if (this.constraintHandler.currentConstraint != null &&
            this.constraintHandler.currentFocus != null &&
            this.constraintHandler.currentPoint != null) {
            constraint = this.constraintHandler.currentConstraint;
            current = this.constraintHandler.currentPoint.clone();
          }
          else if (this.previous != null && !this.graph.isIgnoreTerminalEvent(me.getEvent()) &&
            mxEvent.isShiftDown(me.getEvent())) {
            if (Math.abs(this.previous.getCenterX() - point.x) <
              Math.abs(this.previous.getCenterY() - point.y)) {
              point.x = this.previous.getCenterX();
            } else {
              point.y = this.previous.getCenterY();
            }
          }

          var pt2 = this.first;

          //// Moves the connect icon with the mouse
          if (this.selectedIcon != null) {
            var w = this.selectedIcon.bounds.width;
            var h = this.selectedIcon.bounds.height;

            if (this.currentState != null && this.targetConnectImage) {
              var pos = this.getIconPosition(this.selectedIcon, this.currentState);
              this.selectedIcon.bounds.x = pos.x;
              this.selectedIcon.bounds.y = pos.y;
            } else {
              var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x,
                me.getGraphY() + this.connectIconOffset.y, w, h);
              this.selectedIcon.bounds = bounds;
            }

            this.selectedIcon.redraw();
          }

          if (this.edgeState != null) {
            this.updateEdgeState(current, constraint);
            //// current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];
            var next = (this.waypoints != null && this.waypoints.length > 0) ?
              this.waypoints[0] : current;
            pt2 = this.getSourcePerimeterPoint(this.previous, next, me);
            //// this.edgeState.absolutePoints[0];
          } else {
            if (this.currentState != null) {
              if (this.constraintHandler.currentConstraint == null) {
                var tmp = this.getTargetPerimeterPoint(this.currentState, me);

                if (tmp != null) {
                  current = tmp;
                }
              }
            }

            //// Computes the source perimeter point
            if (this.sourceConstraint == null && this.previous != null)
            {
              var next = (this.waypoints != null && this.waypoints.length > 0) ?
                this.waypoints[0] : current;
              var tmp = this.getSourcePerimeterPoint(this.previous, next, me);

              if (tmp != null)
              {
                pt2 = tmp;
              }
            }
          }

          //// Makes sure the cell under the mousepointer can be detected
          //// by moving the preview shape away from the mouse. This
          //// makes sure the preview shape does not prevent the detection
          //// of the cell under the mousepointer even for slow gestures.
          if (this.currentState == null && this.movePreviewAway) {
            var tmp = pt2;

            if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2) {
              var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2];

              if (tmp2 != null) {
                tmp = tmp2;
              }
            }

            var dx = current.x - tmp.x;
            var dy = current.y - tmp.y;

            var len = Math.sqrt(dx * dx + dy * dy);

            if (len == 0) {
              return;
            }

            //// Stores old point to reuse when creating edge
            this.originalPoint = current.clone();
            current.x -= dx * 4 / len;
            current.y -= dy * 4 / len;
          } else {
            this.originalPoint = null;
          }

          //// Creates the preview shape (lazy)
          if (this.shape == null) {
            var dx = Math.abs(me.getGraphX() - this.first.x);
            var dy = Math.abs(me.getGraphY() - this.first.y);

            if (dx > this.graph.tolerance || dy > this.graph.tolerance) {
              this.shape = this.createShape();

              if (this.edgeState != null) {
                this.shape.apply(this.edgeState);
              }

              //// Revalidates current connection
              this.updateCurrentState(me, point);
            }
          }

          //// Updates the points in the preview edge
          if (this.shape != null) {

            var pts = [pt2];

            if (this.waypoints != null) {
              pts = pts.concat(this.waypoints);
            }

            pts.push(current);
            this.shape.points = pts;

            this.drawPreview();
          }

          //// Makes sure endpoint of edge is visible during connect
          if (this.cursor != null) {
            this.graph.container.style.cursor = this.cursor;
          }

          mxEvent.consume(me.getEvent());
          me.consume();
        } else if (!this.isEnabled() || !this.graph.isEnabled()) {
          this.constraintHandler.reset();
        } else if (this.previous != this.currentState && this.edgeState == null) {
          this.destroyIcons();

          //// Sets the cursor on the current shape
          if (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null) {
            this.icons = this.createIcons(this.currentState);

            if (this.icons == null) {
              this.currentState.setCursor(mxConstants.CURSOR_CONNECT);
              me.consume();
            }
          }

          this.previous = this.currentState;
        } else if (this.previous == this.currentState && this.currentState != null && this.icons == null &&
          !this.graph.isMouseDown) {
          //// Makes sure that no cursors are changed
          me.consume();
        }

        if (!this.graph.isMouseDown && this.currentState != null && this.icons != null) {
          var hitsIcon = false;
          var target = me.getSource();

          for (var i = 0; i < this.icons.length && !hitsIcon; i++) {
            hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;
          }

          if (!hitsIcon) {
            this.updateIcons(this.currentState, this.icons, me);
          }
        }
      } else {
        this.constraintHandler.reset();
      }
    };

    const connectionHandlerMouseMove = this.connectionHandler.mouseMove;

    this.connectionHandler.mouseMove = function () {
      const prev = this.currentState;
      connectionHandlerMouseMove.apply(this, arguments);

      if (prev != this.currentState) {
        startTime = new Date().getTime();
        timeOnTarget = 0;
      } else {
        timeOnTarget = new Date().getTime() - startTime;
      }
    };

    //// Activates outline connect after 1500ms with touch event or if alt is pressed inside the shape
    //// outlineConnect=0 is a custom style that means do not connect to strokes inside the shape,
    //// or in other words, connect to the shape's perimeter if the highlight is under the mouse
    //// (the name is because the highlight, including all strokes, is called outline in the code)
    const connectionHandleIsOutlineConnectEvent = this.connectionHandler
      .isOutlineConnectEvent;

    this.connectionHandler.isOutlineConnectEvent = function (me) {
      return (
        (this.currentState != null &&
          me.getState() == this.currentState &&
          timeOnTarget > 2000) ||
        ((this.currentState == null ||
          mxUtils.getValue(this.currentState.style, "outlineConnect", "1") !=
          "0") &&
          connectionHandleIsOutlineConnectEvent.apply(this, arguments))
      );
    };

    //// Adds shift+click to toggle selection state
    const isToggleEvent = this.isToggleEvent;
    this.isToggleEvent = function (evt) {
      return (
        isToggleEvent.apply(this, arguments) ||
        (!mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt))
      );
    };

    //// Workaround for Firefox where first mouse down is received
    //// after tap and hold if scrollbars are visible, which means
    //// start rubberband immediately if no cell is under mouse.
    const isForceRubberBandEvent = this.rubberband.isForceRubberbandEvent;
    this.rubberband.isForceRubberbandEvent = function (me) {
      return (
        isForceRubberBandEvent.apply(this, arguments) ||
        (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(me.getEvent())) ||
        (mxUtils.hasScrollbars(this.graph.container) &&
          mxClient.IS_FF &&
          mxClient.IS_WIN &&
          me.getState() == null &&
          mxEvent.isTouchEvent(me.getEvent()))
      );
    };

    //// Shows hand cursor while panning
    let prevCursor = null;

    this.panningHandler.addListener(
      mxEvent.PAN_START,
      mxUtils.bind(this, function () {
        if (this.isEnabled()) {
          prevCursor = this.container.style.cursor;
          this.container.style.cursor = "move";
        }
      })
    );

    this.panningHandler.addListener(
      mxEvent.PAN_END,
      mxUtils.bind(this, function () {
        if (this.isEnabled()) {
          this.container.style.cursor = prevCursor;
        }
      })
    );

    this.popupMenuHandler.autoExpand = true;

    this.popupMenuHandler.isSelectOnPopup = function (me) {
      return mxEvent.isMouseEvent(me.getEvent());
    };

    //// Handles links if graph is read-only or cell is locked
    const click = this.click;
    this.click = function (me) {
      const locked =
        me.state == null &&
        me.sourceState != null &&
        this.isCellLocked(me.sourceState.cell);

      if ((!this.isEnabled() || locked) && !me.isConsumed()) {
        const cell = locked ? me.sourceState.cell : me.getCell();

        if (cell != null) {
          const link = this.getLinkForCell(cell);

          if (link != null) {
            if (this.isCustomLink(link)) {
              this.customLinkClicked(link);
            } else {
              this.openLink(link);
            }
          }
        }

        if (this.isEnabled() && locked) {
          this.clearSelection();
        }
      } else {
        return click.apply(this, arguments);
      }
    };

    //// Redirects tooltips for locked cells
    this.tooltipHandler.getStateForEvent = function (me) {
      return me.sourceState;
    };

    //// Redirects cursor for locked cells
    const getCursorForMouseEvent = this.getCursorForMouseEvent;
    this.getCursorForMouseEvent = function (me) {
      const locked =
        me.state == null &&
        me.sourceState != null &&
        this.isCellLocked(me.sourceState.cell);

      return this.getCursorForCell(locked ? me.sourceState.cell : me.getCell());
    };

    //// Shows pointer cursor for clickable cells with links
    //// ie. if the graph is disabled and cells cannot be selected
    const getCursorForCell = this.getCursorForCell;
    this.getCursorForCell = function (cell) {
      if (!this.isEnabled() || this.isCellLocked(cell)) {
        const link = this.getLinkForCell(cell);

        if (link != null) {
          return "pointer";
        } else if (this.isCellLocked(cell)) {
          return "default";
        }
      }

      return getCursorForCell.apply(this, arguments);
    };

    //// Changes rubberband selection to be recursive
    this.selectRegion = function (rect, evt) {
      const cells = this.getAllCells(rect.x, rect.y, rect.width, rect.height);
      this.selectCellsForEvent(cells, evt);

      return cells;
    };

    //// Recursive implementation for rubberband selection
    this.getAllCells = function (x, y, width, height, parent, result) {
      result = result != null ? result : [];

      if (width > 0 || height > 0) {
        const model = this.getModel();
        const right = x + width;
        const bottom = y + height;

        if (parent == null) {
          parent = this.getCurrentRoot();

          if (parent == null) {
            parent = model.getRoot();
          }
        }

        if (parent != null) {
          const childCount = model.getChildCount(parent);

          for (let i = 0; i < childCount; i++) {
            const cell = model.getChildAt(parent, i);
            const state = this.view.getState(cell);

            if (
              state != null &&
              this.isCellVisible(cell) &&
              mxUtils.getValue(state.style, "locked", "0") != "1"
            ) {
              const deg =
                mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0;
              let box = state;

              if (deg != 0) {
                box = mxUtils.getBoundingBox(box, deg);
              }

              if (
                (model.isEdge(cell) || model.isVertex(cell)) &&
                box.x >= x &&
                box.y + box.height <= bottom &&
                box.y >= y &&
                box.x + box.width <= right
              ) {
                result.push(cell);
              }

              this.getAllCells(x, y, width, height, cell, result);
            }
          }
        }
      }

      return result;
    };

    //// Never removes cells from parents that are being moved
    //// let graphHandlerShouldRemoveCellsFromParent = this.graphHandler
    ////   .shouldRemoveCellsFromParent;
    //// this.graphHandler.shouldRemoveCellsFromParent = function (
    ////   parent,
    ////   cells,
    ////   evt
    //// ) {
    ////   if (this.graph.isCellSelected(parent)) {
    ////     return false;
    ////   }

    ////   return graphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
    //// };

    //// Unlocks all cells
    //// this.isCellLocked = function (cell) {
    ////   let pState = this.view.getState(cell);

    ////   while (pState != null) {
    ////     if (mxUtils.getValue(pState.style, "locked", "0") == "1") {
    ////       return true;
    ////     }

    ////     pState = this.view.getState(this.model.getParent(pState.cell));
    ////   }

    ////   return false;
    //// };

    this.tapAndHoldSelection = null;

    //// Uses this event to process mouseDown to check the selection state before it is changed
    this.addListener(
      mxEvent.FIRE_MOUSE_EVENT,
      mxUtils.bind(this, function (sender, evt) {
        if (evt.getProperty("eventName") == "mouseDown") {
          const me = evt.getProperty("event");
          const state = me.getState();

          if (
            state != null &&
            !this.isSelectionEmpty() &&
            !this.isCellSelected(state.cell)
          ) {
            this.tapAndHoldSelection = this.getSelectionCells();
          } else {
            this.tapAndHoldSelection = null;
          }
        }
      })
    );

    //// Tap and hold on background starts rubberband for multiple selected
    //// cells the cell associated with the event is deselected
    this.addListener(
      mxEvent.TAP_AND_HOLD,
      mxUtils.bind(this, function (sender, evt) {
        console.log('touch----------------');
        if (!mxEvent.isMultiTouchEvent(evt)) {
          const me = evt.getProperty("event");
          const cell = evt.getProperty("cell");

          if (cell == null || cell.deep === 0) {
            const pt = mxUtils.convertPoint(
              this.container,
              mxEvent.getClientX(me),
              mxEvent.getClientY(me)
            );
            this.rubberband.start(pt.x, pt.y);
          } else if ( this.tapAndHoldSelection != null) {
            this.addSelectionCells( this.tapAndHoldSelection);
          } else if (
            this.getSelectionCount() > 1 &&
            this.isCellSelected(cell)
          ) {
            this.removeSelectionCell(cell);
          }

          //// Blocks further processing of the event
          this.tapAndHoldSelection = null;
          evt.consume();
        }
      })
    );

    //// On connect the target is selected and we clone the cell of the preview edge for insert
    this.connectionHandler.selectCells = function (edge, target) {
      this.graph.setSelectionCell(target || edge);
    };

    //// Shows connection points only if cell not selected
    this.connectionHandler.constraintHandler.isStateIgnored = function (
      state,
      source
    ) {
      return source && state.view.graph.isCellSelected(state.cell);
    };

    //// Updates constraint handler if the selection changes
    this.selectionModel.addListener(
      mxEvent.CHANGE,
      mxUtils.bind(this, function () {
        const ch = this.connectionHandler.constraintHandler;

        if (
          ch.currentFocus != null &&
          ch.isStateIgnored(ch.currentFocus, true)
        ) {
          ch.currentFocus = null;
          ch.constraints = null;
          ch.destroyIcons();
        }

        ch.destroyFocusHighlight();
      })
    );

    //// Initializes touch interface
    if (Graph.touchStyle) {
      this.initTouch();
    }

    /**
     * Adds locking
     */
    const graphUpdateMouseEvent = this.updateMouseEvent;
    this.updateMouseEvent = function (me) {
      me = graphUpdateMouseEvent.apply(this, arguments);

      if (me.state != null && this.isCellLocked(me.getCell())) {
        me.state = null;
      }

      return me;
    };
  }

  //// mxOutline.prototype.mouseMove = function(sender, me){
  ////   console.log(sender);
  ////   console.log(me);

  //// }

  //// if( typeof mxSelectionCellsHandler !== "undefined" ) {
  ////   //// mxSelectionCellsHandler.prototype.state = null
  ////   mxSelectionCellsHandler.prototype.mouseDown = function(  sender, me  ) {
  ////     //// startIndex = 0
  ////     //// console.log(88888);
  ////       if( me ) {
  ////         //// console.log("mememememememememememe",me);

  ////         //// mxSelectionCellsHandler.prototype.state =
  ////       }

  ////   }
  ////   mxSelectionCellsHandler.prototype.mouseMove  = function(  sender, me  ) {
  ////     if( me.state ) {
  ////       //// let bds = mxRectangle.fromRectangle(me.state);
  ////       //// console.log(bds);
  ////       //// console.log(me);
  ////       }
  ////       //// this.x = me.graphX
  ////       //// this.y = me.graphY
  ////   console.log("mxDragSource.prototype.dragElement", mxDragSource.prototype);

  ////   }
  ////   mxSelectionCellsHandler.prototype.mouseUp = function(  sender, me  ) {
  ////     //// console.log(this.x);
  ////     //// console.log(this.y);
  ////     if(me.state) {
  ////       let bds = mxRectangle.fromRectangle(me.state);
  ////       console.log(bds);

  ////     }

  ////     //// startIndex = 1
  ////     ////  console.log("sender:",sender);
  ////     ////   console.log("me:",me);
  ////       //// sender.isMouseDown = true
  ////   }

  //// }

  //// Create a unique offset object for each graph instance.
  this.currentTranslate = new mxPoint(0, 0);

};

/**
 * Specifies if the touch UI should be used (cannot detect touch in FF so always on for Windows/Linux)
 */
Graph.touchStyle =
  mxClient.IS_TOUCH ||
  (mxClient.IS_FF && mxClient.IS_WIN) ||
  navigator.maxTouchPoints > 0 ||
  navigator.msMaxTouchPoints > 0 ||
  window.urlParams == null ||
  urlParams["touch"] == "1";

/**
 * Shortcut for capability check.
 */
Graph.fileSupport =
  window.File != null &&
  window.FileReader != null &&
  window.FileList != null &&
  (window.urlParams == null || urlParams["filesupport"] != "0");

/**
 * Default size for line jumps.
 */
Graph.lineJumpsEnabled = true;

/**
 * Default size for line jumps.
 */
Graph.defaultJumpSize = 6;

/**
 * Helper function for creating SVG data .
 */
//// 箭头的图片
Graph.createSvgImage = function (w, h, data) {
  const tmp = unescape(
    encodeURIComponent(
      '<!DOCTYPE svg PUBLIC "-////W3C////DTD SVG 1.1////EN" "http:////www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' +
      '<svg xmlns="http:////www.w3.org/2000/svg" xmlns:xlink="http:////www.w3.org/1999/xlink" width="' +
      w +
      'px" height="' +
      h +
      'px" ' +
      'version="1.1">' +
      data +
      "</svg>"
    )
  );

  return new mxImage(
    "data:image/svg+xml;base64," +
    (window.btoa ? btoa(tmp) : Base64.encode(tmp, true)),
    w,
    h
  );
};

/**
 * Removes all illegal control characters with ASCII code <32 except TAB, LF
 * and CR.
 */
Graph.zapGremlins = function (text) {
  const checked = [];

  for (let i = 0; i < text.length; i++) {
    const code = text.charCodeAt(i);

    //// Removes all control chars except TAB, LF and CR
    if (
      (code >= 32 || code == 9 || code == 10 || code == 13) &&
      code != 0xffff &&
      code != 0xfffe
    ) {
      checked.push(text.charAt(i));
    }
  }

  return checked.join("");
};

/**
 * Turns the given string into an array.
 */
Graph.stringToBytes = function (str) {
  const arr = new Array(str.length);

  for (let i = 0; i < str.length; i++) {
    arr[i] = str.charCodeAt(i);
  }

  return arr;
};

/**
 * Turns the given array into a string.
 */
Graph.bytesToString = function (arr) {
  const result = new Array(arr.length);

  for (let i = 0; i < arr.length; i++) {
    result[i] = String.fromCharCode(arr[i]);
  }

  return result.join("");
};

/**
 * Returns a base64 encoded version of the compressed outer XML of the given node.
 */
Graph.compressNode = function (node) {
  return Graph.compress(Graph.zapGremlins(mxUtils.getXml(node)));
};

/**
 * Returns a base64 encoded version of the compressed string.
 */
Graph.compress = function (data, deflate) {
  if (data == null || data.length == 0 || typeof pako === "undefined") {
    return data;
  } else {
    const tmp = Graph.bytesToString(
      deflate
        ? pako.deflate(encodeURIComponent(data))
        : pako.deflateRaw(encodeURIComponent(data))
    );

    return window.btoa ? btoa(tmp) : Base64.encode(tmp, true);
  }
};

/**
 * Returns a decompressed version of the base64 encoded string.
 */
Graph.decompress = function (data, inflate) {
  //// console.log(data, 'xml');
  if (data == null || data.length == 0 || typeof pako === "undefined") {
    return data;
  } else {
    const tmp = window.atob ? atob(data) : Base64.decode(data, true);

    return Graph.zapGremlins(
      decodeURIComponent(
        Graph.bytesToString(inflate ? pako.inflate(tmp) : pako.inflateRaw(tmp))
      )
    );
  }
};

/**
 * Graph inherits from mxGraph.
 */
mxUtils.extend(Graph, mxGraph);

/**
 * Allows all values in fit.
 */
Graph.prototype.minFitScale = null;

/**
 * Allows all values in fit.
 */
Graph.prototype.maxFitScale = null;

/**
 * Sets the policy for links. Possible values are "self" to replace any framesets,
 * "blank" to load the URL in <linkTarget> and "auto" (default).
 */
Graph.prototype.linkPolicy =
  urlParams["target"] == "frame" ? "blank" : urlParams["target"] || "auto";

/**
 * Target for links that open in a new window. Default is _blank.
 */
//// Graph.prototype.linkTarget = (urlParams['target'] == 'frame') ? '_self' : '_blank';
Graph.prototype.linkTarget = urlParams["target"] == "frame" ? "_self" : "_self";

/**
 * Value to the rel attribute of links. Default is 'nofollow noopener noreferrer'.
 * NOTE: There are security implications when this is changed and if noopener is removed,
 * then <openLink> must be overridden to allow for the opener to be set by default.
 */
Graph.prototype.linkRelation = "nofollow noopener noreferrer";

/**
 * Scrollbars are enabled on non-touch devices (not including Firefox because touch events
 * cannot be detected in Firefox, see above).
 */
Graph.prototype.defaultScrollbars = !mxClient.IS_IOS;

/**
 * Specifies if the page should be visible for new files. Default is true.
 */
Graph.prototype.defaultPageVisible = false;

/**
 * Specifies if the app should run in chromeless mode. Default is false.
 * This default is only used if the contructor argument is null.
 */
Graph.prototype.lightbox = false;

/**
 *
 */
Graph.prototype.defaultPageBackgroundColor = "#ffffff";

/**
 *
 */
Graph.prototype.defaultPageBorderColor = "black";

/**
 * Specifies the size of the size for "tiles" to be used for a graph with
 * scrollbars but no visible background page. A good value is large
 * enough to reduce the number of repaints that is caused for auto-
 * translation, which depends on this value, and small enough to give
 * a small empty buffer around the graph. Default is 400x400.
 */
Graph.prototype.scrollTileSize = new mxRectangle(0, 0, 200, 200);

/**
 * Overrides the background color and paints a transparent background.
 */
Graph.prototype.transparentBackground = true;

/**
 * Sets global constants.
 */
Graph.prototype.selectParentAfterDelete = false;

/**
 * Sets the default target for all links in cells.
 */
Graph.prototype.defaultEdgeLength = 80;

/**
 * Disables move of bends/segments without selecting.
 */
Graph.prototype.edgeMode = true;

/**
 * Allows all values in fit.
 */
Graph.prototype.connectionArrowsEnabled = true;

/**
 * Specifies the regular expression for matching placeholders.
 */
Graph.prototype.placeholderPattern = new RegExp("%(date{.*}|[^%^{^}]+)%", "g");

/**
 * Specifies the regular expression for matching placeholders.
 */
Graph.prototype.absoluteUrlPattern = new RegExp("^(?:[a-z]+:)?////", "i");

/**
 * Specifies the default name for the theme. Default is 'default'.
 */
Graph.prototype.defaultThemeName = "default";

/**
 * Specifies the default name for the theme. Default is 'default'.
 */
Graph.prototype.defaultThemes = {};

/**
 * Base URL for relative links.
 */
Graph.prototype.baseUrl =
  urlParams["base"] != null
    ? decodeURIComponent(urlParams["base"])
    : (window != window.top
      ? document.referrer
      : document.location.toString()
    ).split("#")[0];

/**
 * Specifies if the label should be edited after an insert.
 */
Graph.prototype.editAfterInsert = true;

/**
 * Defines the built-in properties to be ignored in tooltips.
 */
Graph.prototype.builtInProperties = [
  "label",
  "tooltip",
  "placeholders",
  "placeholder"
];

//// Graph.prototype.diagramTitleExist = false;

//// Graph.prototype.diagramTitleId = undefined;

Graph.prototype.diagram = null;

/**
 * Installs child layout styles.
 */
Graph.prototype.init = function () {
  mxGraph.prototype.init.apply(this, arguments);

  //// Intercepts links with no target attribute and opens in new window
  this.cellRenderer.initializeLabel = function (state, shape) {
    mxCellRenderer.prototype.initializeLabel.apply(this, arguments);

    //// Checks tolerance for clicks on links
    const tol = state.view.graph.tolerance;
    let handleClick = true;
    let first = null;

    const down = mxUtils.bind(this, function (evt) {
      handleClick = true;
      first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
    });

    var move = mxUtils.bind(this, function (evt) {
      handleClick =
        handleClick &&
        first != null &&
        Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
        Math.abs(first.y - mxEvent.getClientY(evt)) < tol;
    });

    const up = mxUtils.bind(this, function (evt) {
      if (handleClick) {
        let elt = mxEvent.getSource(evt);

        while (elt != null && elt != shape.node) {
          if (elt.nodeName.toLowerCase() == "a") {
            state.view.graph.labelLinkClicked(state, elt, evt);
            break;
          }

          elt = elt.parentNode;
        }
      }
    });

    mxEvent.addGestureListeners(shape.node, down, move, up);
    mxEvent.addListener(shape.node, "click", function (evt) {
      mxEvent.consume(evt);
    });
  };

  this.diagramTitleId = generateUUID().toString();

  this.initLayoutManager();
};

/**
 * Implements zoom and offset via CSS transforms. This is currently only used
 * in read-only as there are fewer issues with the mxCellState not being scaled
 * and translated.
 *
 * KNOWN ISSUES TO FIX:
 * - Apply CSS transforms to HTML labels in IE11
 */
(function () {
  /**
   * Uses CSS transforms for scale and translate.
   */
  Graph.prototype.useCssTransforms = false;

  /**
   * Contains the scale.
   */
  Graph.prototype.currentScale = 1;

  /**
   * Contains the offset.
   */
  Graph.prototype.currentTranslate = new mxPoint(0, 0);

  /**
   * Only foreignObject supported for now (no IE11).
   */
  Graph.prototype.isCssTransformsSupported = function () {
    return this.dialect == mxConstants.DIALECT_SVG && !mxClient.NO_FO;
  };

  /**
   * Function: getCellAt
   *
   * Needs to modify original method for recursive call.
   */
  Graph.prototype.getCellAt = function (
    x,
    y,
    parent,
    vertices,
    edges,
    ignoreFn
  ) {
    if (this.useCssTransforms) {
      x = x / this.currentScale - this.currentTranslate.x;
      y = y / this.currentScale - this.currentTranslate.y;
    }

    return this.getScaledCellAt.apply(this, arguments);
  };

  /**
   * Function: getScaledCellAt
   *
   * Overridden for recursion.
   */
  Graph.prototype.getScaledCellAt = function (
    x,
    y,
    parent,
    vertices,
    edges,
    ignoreFn
  ) {
    vertices = vertices != null ? vertices : true;
    edges = edges != null ? edges : true;

    if (parent == null) {
      parent = this.getCurrentRoot();

      if (parent == null) {
        parent = this.getModel().getRoot();
      }
    }

    if (parent != null) {
      const childCount = this.model.getChildCount(parent);

      for (let i = childCount - 1; i >= 0; i--) {
        const cell = this.model.getChildAt(parent, i);
        //// todo 缩放
        const result = this.getScaledCellAt(
          x,
          y,
          cell,
          vertices,
          edges,
          ignoreFn
        );

        if (result != null) {
          return result;
        } else if (
          this.isCellVisible(cell) &&
          ((edges && this.model.isEdge(cell)) ||
            (vertices && this.model.isVertex(cell)))
        ) {
          const state = this.view.getState(cell);

          if (
            state != null &&
            (ignoreFn == null || !ignoreFn(state, x, y)) &&
            this.intersects(state, x, y)
          ) {
            return cell;
          }
        }
      }
    }

    return null;
  };

  /**
   * Function: repaint
   *
   * Updates the highlight after a change of the model or view.
   */
  mxCellHighlight.prototype.getStrokeWidth = function (state) {
    let s = this.strokeWidth;

    if (this.graph.useCssTransforms) {
      s /= this.graph.currentScale;
    }

    return s;
  };

  /**
   * Function: updateCssTransform
   *
   * Zooms out of the graph by <zoomFactor>.
   */
  Graph.prototype.updateCssTransform = function () {
    const temp = this.view.getDrawPane();

    if (temp != null) {
      const g = temp.parentNode;

      if (!this.useCssTransforms) {
        g.removeAttribute("transformOrigin");
        g.removeAttribute("transform");
      } else {
        const prev = g.getAttribute("transform");
        g.setAttribute("transformOrigin", "0 0");
        g.setAttribute(
          "transform",
          "scale(" +
          this.currentScale +
          "," +
          this.currentScale +
          ")" +
          "translate(" +
          this.currentTranslate.x +
          "," +
          this.currentTranslate.y +
          ")"
        );

        //// Applies workarounds only if translate has changed
        if (prev != g.getAttribute("transform")) {
          try {
            //// Applies transform to labels outside of the SVG DOM
            //// Excluded via isCssTransformsSupported
            if (mxClient.NO_FO) {
              const transform =
                "scale(" +
                this.currentScale +
                ")" +
                "translate(" +
                this.currentTranslate.x +
                "px," +
                this.currentTranslate.y +
                "px)";

              this.view.states.visit(
                mxUtils.bind(this, function (cell, state) {
                  if (state.text != null && state.text.node != null) {
                    //// Stores initial CSS transform that is used for the label alignment
                    if (state.text.originalTransform == null) {
                      state.text.originalTransform =
                        state.text.node.style.transform;
                    }
                    state.text.node.style.transform =
                      transform + state.text.originalTransform;
                  }
                })
              );
            }
            //// Workaround for https:////developer.microsoft.com/en-us/microsoft-edge/platform/issues/4320441/
            if (mxClient.IS_EDGE) {
              //// Recommended workaround is to do this on all
              //// foreignObjects, but this seems to be faster
              const val = g.style.display;
              g.style.display = "none";
              g.getBBox();
              g.style.display = val;
            }
          } catch (e) {
            //// ignore
          }
        }
      }
    }
  };

  const graphUpdatePageBreaks = mxGraph.prototype.updatePageBreaks;

  mxGraph.prototype.updatePageBreaks = function (visible, width, height) {
    const useCssTranforms = this.useCssTransforms,
      scale = this.view.scale,
      translate = this.view.translate;

    if (useCssTranforms) {
      this.view.scale = 1;
      this.view.translate = new mxPoint(0, 0);
      this.useCssTransforms = false;
    }

    graphUpdatePageBreaks.apply(this, arguments);

    if (useCssTranforms) {
      this.view.scale = scale;
      this.view.translate = translate;
      this.useCssTransforms = true;
    }
  };
})();

//// Graph.prototype.updateDiagramTitle = function (diagram) {
////   if (this.diagramTitleExist) {
////     this.view.canvas.removeChild(document.getElementById(this.diagramTitleId));
////     this.diagramTitleExist = false;
////   }
////   //// create diagram title area
////   let bounds = this.view.getBackgroundPageBounds();
////   let w = bounds.width;
////   let h = bounds.height;
////   let tcanvas = this.view.createDiagramTitle(w, h, diagram);
////   this.view.canvas.appendChild(tcanvas);
////   this.diagramTitleExist = true;
//// };

/**
 * Sets the XML node for the current diagram.
 */
Graph.prototype.isLightboxView = function () {
  return this.lightbox;
};

//// 增加连接状态，连接第一点
const ConnectStatus = {};
ConnectStatus.notConnect = 0;
ConnectStatus.connectWaiting = 1;
ConnectStatus.connectFirstNode = 2;

Graph.prototype.inConnecting = ConnectStatus.notConnect;

/**
 * Installs automatic layout via styles
 */
Graph.prototype.labelLinkClicked = function (state, elt, evt) {
  const href = elt.getAttribute("href");

  if (
    (href != null &&
      !this.isCustomLink(href) &&
      mxEvent.isLeftMouseButton(evt) && !mxEvent.isPopupTrigger(evt)) ||
    mxEvent.isTouchEvent(evt)
  ) {
    if (!this.isEnabled() || this.isCellLocked(state.cell)) {
      const target = this.isBlankLink(href) ? this.linkTarget : "_top";
      this.openLink(this.getAbsoluteUrl(href), target);
    }

    mxEvent.consume(evt);
  }
};

/**
 * Returns the size of the page format scaled with the page size.
 */
Graph.prototype.openLink = function (href, target, allowOpener) {
  let result = window;

  try {
    //// Workaround for blocking in same iframe
    if (target == "_self" && window != window.top) {
      window.location.href = href;
    } else {
      //// Avoids page reload for anchors (workaround for IE but used everywhere)
      if (
        href.substring(0, this.baseUrl.length) == this.baseUrl &&
        href.charAt(this.baseUrl.length) == "#" &&
        target == "_top" &&
        window == window.top
      ) {
        const hash = href.split("#")[1];

        //// Forces navigation if on same hash
        if (window.location.hash == "#" + hash) {
          window.location.hash = "";
        }

        window.location.hash = hash;
      } else {
        result = window.open(href, target);
        if (result != null && !allowOpener) {
          result.opener = null;
        }
      }
    }
  } catch (e) {
    //// ignores permission denied
  }

  return result;
};

/**
 * Adds support for page links.
 */
Graph.prototype.getLinkTitle = function (href) {
  return href.substring(href.lastIndexOf("/") + 1);
};

/**
 * Adds support for page links.
 */
Graph.prototype.isCustomLink = function (href) {
  return href.substring(0, 5) == "data:";
};

/**
 * Adds support for page links.
 */
Graph.prototype.customLinkClicked = function (link) {
  return false;
};

/**
 * Returns true if the fiven href references an external protocol that
 * should never open in a new window. Default returns true for mailto.
 */
Graph.prototype.isExternalProtocol = function (href) {
  return href.substring(0, 7) === "mailto:";
};

/**
 * Hook for links to open in same window. Default returns true for anchors,
 * links to same domain or if target == 'self' in the config.
 */
Graph.prototype.isBlankLink = function (href) {
  return (
    !this.isExternalProtocol(href) &&
    (this.linkPolicy === "blank" ||
      (this.linkPolicy !== "self" &&
        !this.isRelativeUrl(href) &&
        href.substring(0, this.domainUrl.length) !== this.domainUrl))
  );
};

/**
 *
 */
Graph.prototype.isRelativeUrl = function (url) {
  return (
    url != null &&
    !this.absoluteUrlPattern.test(url) &&
    url.substring(0, 5) !== "data:" &&
    !this.isExternalProtocol(url)
  );
};

/**
 *
 */
Graph.prototype.getAbsoluteUrl = function (url) {
  if (url != null && this.isRelativeUrl(url)) {
    if (url.charAt(0) == "#") {
      url = this.baseUrl + url;
    } else if (url.charAt(0) == "/") {
      url = this.domainUrl + url;
    } else {
      url = this.domainPathUrl + url;
    }
  }

  return url;
};

/**
 * Installs automatic layout via styles
 */
Graph.prototype.initLayoutManager = function () {
  this.layoutManager = new mxLayoutManager(this);

  this.layoutManager.getLayout = function (cell) {
    //// Workaround for possible invalid style after change and before view validation
    const style = this.graph.getCellStyle(cell);

    if (style != null) {
      if (style["childLayout"] == "stackLayout") {
        const stackLayout = new mxStackLayout(this.graph, true);
        stackLayout.resizeParentMax =
          mxUtils.getValue(style, "resizeParentMax", "1") == "1";
        stackLayout.horizontal =
          mxUtils.getValue(style, "horizontalStack", "1") == "1";
        stackLayout.resizeParent =
          mxUtils.getValue(style, "resizeParent", "1") == "1";
        stackLayout.resizeLast =
          mxUtils.getValue(style, "resizeLast", "0") == "1";
        stackLayout.spacing = style["stackSpacing"] || stackLayout.spacing;
        stackLayout.border = style["stackBorder"] || stackLayout.border;
        stackLayout.marginLeft = style["marginLeft"] || 0;
        stackLayout.marginRight = style["marginRight"] || 0;
        stackLayout.marginTop = style["marginTop"] || 0;
        stackLayout.marginBottom = style["marginBottom"] || 0;
        stackLayout.fill = true;

        return stackLayout;
      } else if (style["childLayout"] == "treeLayout") {
        const treeLayout = new mxCompactTreeLayout(this.graph);
        treeLayout.horizontal =
          mxUtils.getValue(style, "horizontalTree", "1") == "1";
        treeLayout.resizeParent =
          mxUtils.getValue(style, "resizeParent", "1") == "1";
        treeLayout.groupPadding = mxUtils.getValue(style, "parentPadding", 20);
        treeLayout.levelDistance = mxUtils.getValue(
          style,
          "treeLevelDistance",
          30
        );
        treeLayout.maintainParentLocation = true;
        treeLayout.edgeRouting = false;
        treeLayout.resetEdges = false;

        return treeLayout;
      } else if (style["childLayout"] == "flowLayout") {
        const flowLayout = new mxHierarchicalLayout(
          this.graph,
          mxUtils.getValue(style, "flowOrientation", mxConstants.DIRECTION_EAST)
        );
        flowLayout.resizeParent =
          mxUtils.getValue(style, "resizeParent", "1") == "1";
        flowLayout.parentBorder = mxUtils.getValue(style, "parentPadding", 20);
        flowLayout.maintainParentLocation = true;

        //// Special undocumented styles for changing the hierarchical
        flowLayout.intraCellSpacing = mxUtils.getValue(
          style,
          "intraCellSpacing",
          mxHierarchicalLayout.prototype.intraCellSpacing
        );
        flowLayout.interRankCellSpacing = mxUtils.getValue(
          style,
          "interRankCellSpacing",
          mxHierarchicalLayout.prototype.interRankCellSpacing
        );
        flowLayout.interHierarchySpacing = mxUtils.getValue(
          style,
          "interHierarchySpacing",
          mxHierarchicalLayout.prototype.interHierarchySpacing
        );
        flowLayout.parallelEdgeSpacing = mxUtils.getValue(
          style,
          "parallelEdgeSpacing",
          mxHierarchicalLayout.prototype.parallelEdgeSpacing
        );

        return flowLayout;
      }
    }

    return null;
  };
};

/**
 * Returns the size of the page format scaled with the page size.
 */
Graph.prototype.getPageSize = function () {
  return this.pageVisible
    ? new mxRectangle(
      0,
      0,
      this.pageFormat.width * this.pageScale,
      this.pageFormat.height * this.pageScale
    )
    : this.scrollTileSize;
};

/**
 * Returns a rectangle describing the position and count of the
 * background pages, where x and y are the position of the top,
 * left page and width and height are the vertical and horizontal
 * page count.
 */
Graph.prototype.getPageLayout = function () {
  const size = this.getPageSize();
  const bounds = this.getGraphBounds();

  if (bounds.width == 0 || bounds.height == 0) {
    return new mxRectangle(0, 0, 1, 1);
  } else {
    //// Computes untransformed graph bounds
    const x = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
    const y = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
    const w = Math.floor(bounds.width / this.view.scale);
    const h = Math.floor(bounds.height / this.view.scale);

    const x0 = Math.floor(x / size.width);
    const y0 = Math.floor(y / size.height);
    const w0 = Math.ceil((x + w) / size.width) - x0;
    const h0 = Math.ceil((y + h) / size.height) - y0;

    return new mxRectangle(x0, y0, w0, h0);
  }
};

/**
 * Sanitizes the given HTML markup.
 */
Graph.prototype.sanitizeHtml = function (value, editing) {
  //// Uses https:////code.google.com/p/google-caja/wiki/JsHtmlSanitizer
  //// NOTE: Original minimized sanitizer was modified to support
  //// data URIs for images, mailto and special data:-links.
  //// LATER: Add MathML to whitelisted tags
  function urlX(link) {
    if (
      link != null &&
      link
        .toString()
        .toLowerCase()
        .substring(0, 11) !== "javascript:"
    ) {
      return link;
    }

    return null;
  }

  function idX(id) {
    return id;
  }

  //// console.log(value, 'sanitizeHtml');
  return value;
  //// return sanitizer.sanitize(value, urlX, idX);
};

/**
 * Revalidates all cells with placeholders in the current graph model.
 */
Graph.prototype.updatePlaceholders = function () {
  const model = this.model;
  let validate = false;

  for (const key in this.model.cells) {
    const cell = this.model.cells[key];

    if (this.isReplacePlaceholders(cell)) {
      this.view.invalidate(cell, false, false);
      validate = true;
    }
  }

  if (validate) {
    this.view.validate();
  }
};

/**
 * Adds support for placeholders in labels.
 */
Graph.prototype.isReplacePlaceholders = function (cell) {
  return (
    cell.value != null &&
    typeof cell.value == "object" &&
    cell.value.getAttribute("placeholders") == "1"
  );
};

/**
 * Returns true if the given mouse wheel event should be used for zooming. This
 * is invoked if no dialogs are showing and returns true with Alt or Control
 * (except macOS) is pressed.
 */
Graph.prototype.isZoomWheelEvent = function (evt) {
  return (
    mxEvent.isAltDown(evt) ||
    (mxEvent.isMetaDown(evt) && mxClient.IS_MAC) ||
    (mxEvent.isControlDown(evt) && !mxClient.IS_MAC)
  );
};

/**
 * Adds Alt+click to select cells behind cells (Shift+Click on Chrome OS).
 */
Graph.prototype.isTransparentClickEvent = function (evt) {
  return (
    mxEvent.isAltDown(evt) || (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt))
  );
};

/**
 * Adds ctrl+shift+connect to disable connections.
 */
Graph.prototype.isIgnoreTerminalEvent = function (evt) {
  return mxEvent.isShiftDown(evt) && mxEvent.isControlDown(evt);
};

/**
 * Adds support for placeholders in labels.
 */
Graph.prototype.isSplitTarget = function (target, cells, evt) {
  return (
    !this.model.isEdge(cells[0]) &&
    !mxEvent.isAltDown(evt) &&
    !mxEvent.isShiftDown(evt) &&
    mxGraph.prototype.isSplitTarget.apply(this, arguments)
  );
};

/**
 * Adds support for placeholders in labels.
 */
Graph.prototype.getLabel = function (cell) {
  let result = mxGraph.prototype.getLabel.apply(this, arguments);

  if (
    result != null &&
    this.isReplacePlaceholders(cell) &&
    cell.getAttribute("placeholder") == null
  ) {
    result = this.replacePlaceholders(cell, result);
  }

  return result;
};

/**
 * Adds labelMovable style.
 */
Graph.prototype.isLabelMovable = function (cell) {
  const state = this.view.getState(cell);
  const style = state != null ? state.style : this.getCellStyle(cell);

  return (
    !this.isCellLocked(cell) &&
    ((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
      (this.model.isVertex(cell) &&
        (this.vertexLabelsMovable ||
          mxUtils.getValue(style, "labelMovable", "0") == "1")))
  );
};

/**
 * Adds event if grid size is changed.
 */
Graph.prototype.setGridSize = function (value) {
  this.gridSize = value;
  this.fireEvent(new mxEventObject("gridSizeChanged"));
};

/**
 * Private helper method.
 */
Graph.prototype.getGlobalVariable = function (name) {
  let val = null;

  if (name == "date") {
    val = new Date().toLocaleDateString();
  } else if (name == "time") {
    val = new Date().toLocaleTimeString();
  } else if (name == "timestamp") {
    val = new Date().toLocaleString();
  } else if (name.substring(0, 5) == "date{") {
    const fmt = name.substring(5, name.length - 1);
    val = this.formatDate(new Date(), fmt);
  }

  return val;
};

/**
 * Formats a date, see http:////blog.stevenlevithan.com/archives/date-time-format
 */
Graph.prototype.formatDate = function (date, mask, utc) {
  //// LATER: Cache regexs
  if (this.dateFormatCache == null) {
    this.dateFormatCache = {
      i18n: {
        dayNames: [
          "Sun",
          "Mon",
          "Tue",
          "Wed",
          "Thu",
          "Fri",
          "Sat",
          "Sunday",
          "Monday",
          "Tuesday",
          "Wednesday",
          "Thursday",
          "Friday",
          "Saturday"
        ],
        monthNames: [
          "Jan",
          "Feb",
          "Mar",
          "Apr",
          "May",
          "Jun",
          "Jul",
          "Aug",
          "Sep",
          "Oct",
          "Nov",
          "Dec",
          "January",
          "February",
          "March",
          "April",
          "May",
          "June",
          "July",
          "August",
          "September",
          "October",
          "November",
          "December"
        ]
      },

      masks: {
        default: "ddd mmm dd yyyy HH:MM:ss",
        shortDate: "m/d/yy",
        mediumDate: "mmm d, yyyy",
        longDate: "mmmm d, yyyy",
        fullDate: "dddd, mmmm d, yyyy",
        shortTime: "h:MM TT",
        mediumTime: "h:MM:ss TT",
        longTime: "h:MM:ss TT Z",
        isoDate: "yyyy-mm-dd",
        isoTime: "HH:MM:ss",
        isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
        isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
      }
    };
  }

  const dF = this.dateFormatCache;
  const token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
    timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
    timezoneClip = /[^-+\dA-Z]/g,
    pad = function (val, len) {
      val = String(val);
      len = len || 2;
      while (val.length < len) val = "0" + val;
      return val;
    };

  //// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
  if (
    arguments.length == 1 &&
    Object.prototype.toString.call(date) == "[object String]" &&
    !/\d/.test(date)
  ) {
    mask = date;
    date = undefined;
  }

  //// Passing date through Date applies Date.parse, if necessary
  date = date ? new Date(date) : new Date();
  if (isNaN(date)) throw SyntaxError("invalid date");

  mask = String(dF.masks[mask] || mask || dF.masks["default"]);

  //// Allow setting the utc argument via the mask
  if (mask.slice(0, 4) == "UTC:") {
    mask = mask.slice(4);
    utc = true;
  }

  const _ = utc ? "getUTC" : "get",
    d = date[_ + "Date"](),
    D = date[_ + "Day"](),
    m = date[_ + "Month"](),
    y = date[_ + "FullYear"](),
    H = date[_ + "Hours"](),
    M = date[_ + "Minutes"](),
    s = date[_ + "Seconds"](),
    L = date[_ + "Milliseconds"](),
    o = utc ? 0 : date.getTimezoneOffset(),
    flags = {
      d: d,
      dd: pad(d),
      ddd: dF.i18n.dayNames[D],
      dddd: dF.i18n.dayNames[D + 7],
      m: m + 1,
      mm: pad(m + 1),
      mmm: dF.i18n.monthNames[m],
      mmmm: dF.i18n.monthNames[m + 12],
      yy: String(y).slice(2),
      yyyy: y,
      h: H % 12 || 12,
      hh: pad(H % 12 || 12),
      H: H,
      HH: pad(H),
      M: M,
      MM: pad(M),
      s: s,
      ss: pad(s),
      l: pad(L, 3),
      L: pad(L > 99 ? Math.round(L / 10) : L),
      t: H < 12 ? "a" : "p",
      tt: H < 12 ? "am" : "pm",
      T: H < 12 ? "A" : "P",
      TT: H < 12 ? "AM" : "PM",
      Z: utc
        ? "UTC"
        : (String(date).match(timezone) || [""])
          .pop()
          .replace(timezoneClip, ""),
      o:
        (o > 0 ? "-" : "+") +
        pad(Math.floor(Math.abs(o) / 60) * 100 + (Math.abs(o) % 60), 4),
      S: ["th", "st", "nd", "rd"][
        d % 10 > 3 ? 0 : (((d % 100) - (d % 10) != 10) * d) % 10
      ]
    };

  return mask.replace(token, function ($0) {
    return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
  });
};

/**
 *
 */
Graph.prototype.createLayersDialog = function () {
  const div = document.createElement("div");
  div.style.position = "absolute";

  const model = this.getModel();
  const childCount = model.getChildCount(model.root);

  for (let i = 0; i < childCount; i++) {
    mxUtils.bind(this, function (layer) {
      const span = document.createElement("div");
      span.style.overflow = "hidden";
      span.style.textOverflow = "ellipsis";
      span.style.padding = "2px";
      span.style.whiteSpace = "nowrap";

      const cb = document.createElement("input");
      cb.style.display = "inline-block";
      cb.setAttribute("type", "checkbox");

      if (model.isVisible(layer)) {
        cb.setAttribute("checked", "checked");
        cb.defaultChecked = true;
      }

      span.appendChild(cb);

      const title =
        this.convertValueToString(layer) ||
        mxResources.get("background") || "Background";
      span.setAttribute("title", title);
      mxUtils.write(span, title);
      div.appendChild(span);

      mxEvent.addListener(cb, "click", function () {
        if (cb.getAttribute("checked") != null) {
          cb.removeAttribute("checked");
        } else {
          cb.setAttribute("checked", "checked");
        }

        model.setVisible(layer, cb.checked);
      });
    })(model.getChildAt(model.root, i));
  }

  return div;
};

/**
 * Private helper method.
 */
Graph.prototype.replacePlaceholders = function (cell, str) {
  const result = [];

  if (str != null) {
    let last = 0;
    let match;
    while ((match = this.placeholderPattern.exec(str))) {
      const val = match[0];

      if (val.length > 2 && val != "%label%" && val != "%tooltip%") {
        let tmp = null;

        if (match.index > last && str.charAt(match.index - 1) == "%") {
          tmp = val.substring(1);
        } else {
          const name = val.substring(1, val.length - 1);

          //// Workaround for invalid char for getting attribute in older versions of IE
          if (name.indexOf("{") < 0) {
            let current = cell;

            while (tmp == null && current != null) {
              if (current.value != null && typeof current.value == "object") {
                tmp = current.hasAttribute(name)
                  ? current.getAttribute(name) != null
                    ? current.getAttribute(name)
                    : ""
                  : null;
              }

              current = this.model.getParent(current);
            }
          }

          if (tmp == null) {
            tmp = this.getGlobalVariable(name);
          }
        }

        result.push(
          str.substring(last, match.index) + (tmp != null ? tmp : val)
        );
        last = match.index + val.length;
      }
    }

    result.push(str.substring(last));
  }

  return result.join("");
};

/**
 * Resolves the given cells in the model and selects them.
 */
Graph.prototype.restoreSelection = function (cells) {
  if (cells != null && cells.length > 0) {
    const temp = [];

    for (let i = 0; i < cells.length; i++) {
      const newCell = this.model.getCell(cells[i].id);

      if (newCell != null) {
        temp.push(newCell);
      }
    }

    this.setSelectionCells(temp);
  } else {
    this.clearSelection();
  }
};

/**
 * Adds a connection to the given vertex.
 */
Graph.prototype.connectVertex = function (
  source,
  direction,
  length,
  evt,
  forceClone,
  ignoreCellAt
) {
  //// Ignores relative edge labels
  if (source.geometry.relative && this.model.isEdge(source.parent)) {
    return [];
  }

  ignoreCellAt = ignoreCellAt ? ignoreCellAt : false;

  const pt =
    source.geometry.relative && source.parent.geometry != null
      ? new mxPoint(
        source.parent.geometry.width * source.geometry.x,
        source.parent.geometry.height * source.geometry.y
      )
      : new mxPoint(source.geometry.x, source.geometry.y);

  if (direction == mxConstants.DIRECTION_NORTH) {
    pt.x += source.geometry.width / 2;
    pt.y -= length;
  } else if (direction == mxConstants.DIRECTION_SOUTH) {
    pt.x += source.geometry.width / 2;
    pt.y += source.geometry.height + length;
  } else if (direction == mxConstants.DIRECTION_WEST) {
    pt.x -= length;
    pt.y += source.geometry.height / 2;
  } else {
    pt.x += source.geometry.width + length;
    pt.y += source.geometry.height / 2;
  }

  const parentState = this.view.getState(this.model.getParent(source));
  const s = this.view.scale;
  const t = this.view.translate;
  let dx = t.x * s;
  let dy = t.y * s;

  if (parentState != null && this.model.isVertex(parentState.cell)) {
    dx = parentState.x;
    dy = parentState.y;
  }

  //// Workaround for relative child cells
  if (this.model.isVertex(source.parent) && source.geometry.relative) {
    pt.x += source.parent.geometry.x;
    pt.y += source.parent.geometry.y;
  }

  //// Checks actual end point of edge for target cell
  let target =
    ignoreCellAt || (mxEvent.isControlDown(evt) && !forceClone)
      ? null
      : this.getCellAt(dx + pt.x * s, dy + pt.y * s);

  if (this.model.isAncestor(target, source)) {
    target = null;
  }

  //// Checks if target or ancestor is locked
  let temp = target;

  while (temp != null) {
    if (this.isCellLocked(temp)) {
      target = null;
      break;
    }

    temp = this.model.getParent(temp);
  }

  //// Checks if source and target intersect
  if (target != null) {
    const sourceState = this.view.getState(source);
    const targetState = this.view.getState(target);

    if (
      sourceState != null &&
      targetState != null &&
      mxUtils.intersects(sourceState, targetState)
    ) {
      target = null;
    }
  }

  const duplicate = !mxEvent.isShiftDown(evt) || forceClone;

  if (duplicate) {
    if (direction == mxConstants.DIRECTION_NORTH) {
      pt.y -= source.geometry.height / 2;
    } else if (direction == mxConstants.DIRECTION_SOUTH) {
      pt.y += source.geometry.height / 2;
    } else if (direction == mxConstants.DIRECTION_WEST) {
      pt.x -= source.geometry.width / 2;
    } else {
      pt.x += source.geometry.width / 2;
    }
  }

  //// Uses connectable parent vertex if one exists
  if (target != null && !this.isCellConnectable(target)) {
    const parent = this.getModel().getParent(target);

    if (this.getModel().isVertex(parent) && this.isCellConnectable(parent)) {
      target = parent;
    }
  }

  if (
    target == source ||
    this.model.isEdge(target) ||
    !this.isCellConnectable(target)
  ) {
    target = null;
  }

  const result = [];

  this.model.beginUpdate();
  try {
    let realTarget = target;

    if (realTarget == null && duplicate) {
      //// Handles relative children
      let cellToClone = source;
      var geo = this.getCellGeometry(source);

      while (geo != null && geo.relative) {
        cellToClone = this.getModel().getParent(cellToClone);
        geo = this.getCellGeometry(cellToClone);
      }

      //// Handle consistuents for cloning
      const state = this.view.getState(cellToClone);
      const style = state != null ? state.style : this.getCellStyle(cellToClone);

      if (mxUtils.getValue(style, "part", false)) {
        const tmpParent = this.model.getParent(cellToClone);

        if (this.model.isVertex(tmpParent)) {
          cellToClone = tmpParent;
        }
      }

      realTarget = this.duplicateCells([cellToClone], false)[0];

      var geo = this.getCellGeometry(realTarget);

      if (geo != null) {
        geo.x = pt.x - geo.width / 2;
        geo.y = pt.y - geo.height / 2;
      }
    }

    //// Never connects children in stack layouts
    let layout = null;

    if (this.layoutManager != null) {
      layout = this.layoutManager.getLayout(this.model.getParent(source));
    }
    //// let edgeCell = new mxCell(
    ////   "",
    ////   new mxGeometry(),
    ////   UMLDIHelper.getRelationStyleByTypeName(curEdgeType)
    //// );
    //// edgeCell.vertex = false;
    //// edgeCell.edge = true;
    let edge = null;
    if ((mxEvent.isControlDown(evt) && duplicate) || (target == null && layout != null && layout.constructor == mxStackLayout)) {
      edge = null;
    } else {
      edge = new mxCell(
        "",
        new mxGeometry(),
        this.createCurrentEdgeStyle(),
      );
      edge.vertex = false;
      edge.edge = true;
      this.addEdge(
        edge,
        this.model.getParent(source),
        source,
        realTarget
      );

    }
    //// let edge =
    ////   (mxEvent.isControlDown(evt) && duplicate) ||
    ////   (target == null && layout != null && layout.constructor == mxStackLayout)
    ////     ? null
    ////     : this.addEdge(
    ////       this.createCurrentEdgeStyle(),
    ////       this.model.getParent(source),
    ////       source,
    ////       realTarget
    ////     );

    console.log('addEdge');

    //// Inserts edge before source
    if (edge != null && this.connectionHandler.insertBeforeSource) {
      var index = null;
      let tmp = source;

      while (
        tmp.parent != null &&
        tmp.geometry != null &&
        tmp.geometry.relative &&
        tmp.parent != edge.parent
      ) {
        tmp = this.model.getParent(tmp);
      }

      if (tmp != null && tmp.parent != null && tmp.parent == edge.parent) {
        var index = tmp.parent.getIndex(tmp);
        this.model.add(tmp.parent, edge, index);
      }
    }

    //// Special case: Click on west icon puts clone before cell
    if (
      target == null &&
      realTarget != null &&
      layout != null &&
      source.parent != null &&
      layout.constructor == mxStackLayout &&
      direction == mxConstants.DIRECTION_WEST
    ) {
      var index = source.parent.getIndex(source);
      this.model.add(source.parent, realTarget, index);
    }

    if (edge != null) {
      result.push(edge);
    }

    if (target == null && realTarget != null) {
      result.push(realTarget);
    }

    if (realTarget == null && edge != null) {
      edge.geometry.setTerminalPoint(pt, false);
    }

    if (edge != null) {
      this.fireEvent(new mxEventObject("cellsInserted", "cells", [edge]));
    }
  } finally {
    this.model.endUpdate();
  }

  return result;
};

/**
 * Returns all labels in the diagram as a string.
 */
Graph.prototype.getIndexableText = function () {
  const tmp = document.createElement("div");
  const labels = [];
  let label = "";

  for (const key in this.model.cells) {
    const cell = this.model.cells[key];

    if (this.model.isVertex(cell) || this.model.isEdge(cell)) {
      if (this.isHtmlLabel(cell)) {
        tmp.innerHTML = this.getLabel(cell);
        label = mxUtils.extractTextWithWhitespace([tmp]);
      } else {
        label = this.getLabel(cell);
      }

      label = mxUtils.trim(label.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, " "));

      if (label.length > 0) {
        labels.push(label);
      }
    }
  }

  return labels.join(" ");
};

/**
 * Returns the label for the given cell.
 */
Graph.prototype.convertValueToString = function (cell) {
  if (cell.value != null && typeof cell.value == "object") {
    if (
      this.isReplacePlaceholders(cell) &&
      cell.getAttribute("placeholder") != null
    ) {
      const name = cell.getAttribute("placeholder");
      let current = cell;
      let result = null;

      while (result == null && current != null) {
        if (current.value != null && typeof current.value == "object") {
          result = current.hasAttribute(name)
            ? current.getAttribute(name) != null
              ? current.getAttribute(name)
              : ""
            : null;
        }

        current = this.model.getParent(current);
      }

      return result || "";
    } else {
      return cell.value.getAttribute("label") || "";
    }
  }

  return mxGraph.prototype.convertValueToString.apply(this, arguments);
};

/**
 * Returns the link for the given cell.
 */
Graph.prototype.getLinksForState = function (state) {
  if (state != null && state.text != null && state.text.node != null) {
    return state.text.node.getElementsByTagName("a");
  }

  return null;
};

/**
 * Returns the link for the given cell.
 */
Graph.prototype.getLinkForCell = function (cell) {
  if (cell.value != null && typeof cell.value == "object") {
    let link = cell.value.getAttribute("link");

    //// Removes links with leading javascript: protocol
    //// TODO: Check more possible attack vectors
    if (link != null && link.toLowerCase().substring(0, 11) === "javascript:") {
      link = link.substring(11);
    }

    return link;
  }

  return null;
};

/**
 * Overrides label orientation for collapsed swimlanes inside stack.
 */
Graph.prototype.getCellStyle = function (cell) {
  const style = mxGraph.prototype.getCellStyle.apply(this, arguments);

  if (cell != null && this.layoutManager != null) {
    const parent = this.model.getParent(cell);

    if (this.model.isVertex(parent) && this.isCellCollapsed(cell)) {
      const layout = this.layoutManager.getLayout(parent);

      if (layout != null && layout.constructor == mxStackLayout) {
        style[mxConstants.STYLE_HORIZONTAL] = !layout.horizontal;
      }
    }
  }

  return style;
};

/**
 * Disables alternate width persistence for stack layout parents
 */
Graph.prototype.updateAlternateBounds = function (cell, geo, willCollapse) {
  if (
    cell != null &&
    geo != null &&
    this.layoutManager != null &&
    geo.alternateBounds != null
  ) {
    const layout = this.layoutManager.getLayout(this.model.getParent(cell));

    if (layout != null && layout.constructor == mxStackLayout) {
      if (layout.horizontal) {
        geo.alternateBounds.height = 0;
      } else {
        geo.alternateBounds.width = 0;
      }
    }
  }

  mxGraph.prototype.updateAlternateBounds.apply(this, arguments);
};

/**
 * Adds Shift+collapse/expand and size management for folding inside stack
 */
Graph.prototype.isMoveCellsEvent = function (evt) {
  return mxEvent.isShiftDown(evt);
};

/**
 * Adds Shift+collapse/expand and size management for folding inside stack
 */
Graph.prototype.foldCells = function (
  collapse,
  recurse,
  cells,
  checkFoldable,
  evt
) {
  recurse = recurse != null ? recurse : false;

  if (cells == null) {
    cells = this.getFoldableCells(this.getSelectionCells(), collapse);
  }

  if (cells != null) {
    this.model.beginUpdate();

    try {
      mxGraph.prototype.foldCells.apply(this, arguments);

      //// Resizes all parent stacks if alt is not pressed
      if (this.layoutManager != null) {
        for (let i = 0; i < cells.length; i++) {
          const state = this.view.getState(cells[i]);
          const geo = this.getCellGeometry(cells[i]);

          if (state != null && geo != null) {
            const dx = Math.round(geo.width - state.width / this.view.scale);
            const dy = Math.round(geo.height - state.height / this.view.scale);

            if (dy != 0 || dx != 0) {
              const parent = this.model.getParent(cells[i]);
              const layout = this.layoutManager.getLayout(parent);

              if (layout == null) {
                //// Moves cells to the right and down after collapse/expand
                if (evt != null && this.isMoveCellsEvent(evt)) {
                  this.moveSiblings(state, parent, dx, dy);
                }
              } else if (
                (evt == null || !mxEvent.isAltDown(evt)) &&
                layout.constructor == mxStackLayout &&
                !layout.resizeLast
              ) {
                this.resizeParentStacks(parent, layout, dx, dy);
              }
            }
          }
        }
      }
    } finally {
      this.model.endUpdate();
    }

    //// Selects cells after folding
    if (this.isEnabled()) {
      this.setSelectionCells(cells);
    }
  }
};

/**
 * Overrides label orientation for collapsed swimlanes inside stack.
 */
Graph.prototype.moveSiblings = function (state, parent, dx, dy) {
  this.model.beginUpdate();
  try {
    const cells = this.getCellsBeyond(state.x, state.y, parent, true, true);

    for (let i = 0; i < cells.length; i++) {
      if (cells[i] != state.cell) {
        const tmp = this.view.getState(cells[i]);
        let geo = this.getCellGeometry(cells[i]);

        if (tmp != null && geo != null) {
          geo = geo.clone();
          geo.translate(
            Math.round(
              dx * Math.max(0, Math.min(1, (tmp.x - state.x) / state.width))
            ),
            Math.round(
              dy * Math.max(0, Math.min(1, (tmp.y - state.y) / state.height))
            )
          );
          this.model.setGeometry(cells[i], geo);
        }
      }
    }
  } finally {
    this.model.endUpdate();
  }
};

/**
 * Overrides label orientation for collapsed swimlanes inside stack.
 */
Graph.prototype.resizeParentStacks = function (parent, layout, dx, dy) {
  if (
    this.layoutManager != null &&
    layout != null &&
    layout.constructor == mxStackLayout &&
    !layout.resizeLast
  ) {
    this.model.beginUpdate();
    try {
      const dir = layout.horizontal;

      //// Bubble resize up for all parent stack layouts with same orientation
      while (
        parent != null &&
        layout != null &&
        layout.constructor == mxStackLayout &&
        layout.horizontal == dir &&
        !layout.resizeLast
      ) {
        let pgeo = this.getCellGeometry(parent);
        const pstate = this.view.getState(parent);

        if (pstate != null && pgeo != null) {
          pgeo = pgeo.clone();

          if (layout.horizontal) {
            pgeo.width +=
              dx + Math.min(0, pstate.width / this.view.scale - pgeo.width);
          } else {
            pgeo.height +=
              dy + Math.min(0, pstate.height / this.view.scale - pgeo.height);
          }

          this.model.setGeometry(parent, pgeo);
        }

        parent = this.model.getParent(parent);
        layout = this.layoutManager.getLayout(parent);
      }
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Disables drill-down for non-swimlanes.
 */
Graph.prototype.isContainer = function (cell) {
  const state = this.view.getState(cell);
  const style = state != null ? state.style : this.getCellStyle(cell);

  if (this.isSwimlane(cell)) {
    return style["container"] != "0";
  } else {
    return style["container"] == "1";
  }
};

/**
 * Adds a connectable style.
 */
Graph.prototype.isCellConnectable = function (cell) {
  const state = this.view.getState(cell);
  const style = state != null ? state.style : this.getCellStyle(cell);

  return style != null && style["connectable"] != null
    ? style["connectable"] != "0"
    : mxGraph.prototype.isCellConnectable.apply(this, arguments);
};

/**
 * Function: selectAll
 *
 * Selects all children of the given parent cell or the children of the
 * default parent if no parent is specified. To select leaf vertices and/or
 * edges use <selectCells>.
 *
 * Parameters:
 *
 * parent - Optional <mxCell> whose children should be selected.
 * Default is <defaultParent>.
 */
Graph.prototype.selectAll = function (parent) {
  parent = parent || this.getDefaultParent();

  if (!this.isCellLocked(parent)) {
    mxGraph.prototype.selectAll.apply(this, arguments);
  }
};

/**
 * Function: selectCells
 *
 * Selects all vertices and/or edges depending on the given boolean
 * arguments recursively, starting at the given parent or the default
 * parent if no parent is specified. Use <selectAll> to select all cells.
 * For vertices, only cells with no children are selected.
 *
 * Parameters:
 *
 * vertices - Boolean indicating if vertices should be selected.
 * edges - Boolean indicating if edges should be selected.
 * parent - Optional <mxCell> that acts as the root of the recursion.
 * Default is <defaultParent>.
 */
Graph.prototype.selectCells = function (vertices, edges, parent) {
  parent = parent || this.getDefaultParent();

  if (!this.isCellLocked(parent)) {
    mxGraph.prototype.selectCells.apply(this, arguments);
  }
};

/**
 * Function: getSwimlaneAt
 *
 * Returns the bottom-most swimlane that intersects the given point (x, y)
 * in the cell hierarchy that starts at the given parent.
 *
 * Parameters:
 *
 * x - X-coordinate of the location to be checked.
 * y - Y-coordinate of the location to be checked.
 * parent - <mxCell> that should be used as the root of the recursion.
 * Default is <defaultParent>.
 */
Graph.prototype.getSwimlaneAt = function (x, y, parent) {
  parent = parent || this.getDefaultParent();

  if (!this.isCellLocked(parent)) {
    return mxGraph.prototype.getSwimlaneAt.apply(this, arguments);
  }

  return null;
};

/**
 * Disables folding for non-swimlanes.
 */
Graph.prototype.isCellFoldable = function (cell) {
  const state = this.view.getState(cell);
  const style = state != null ? state.style : this.getCellStyle(cell);

  return (
    this.foldingEnabled &&
    (style["treeFolding"] == "1" ||
      (!this.isCellLocked(cell) &&
        ((this.isContainer(cell) && style["collapsible"] != "0") ||
          (!this.isContainer(cell) && style["collapsible"] == "1"))))
  );
};

/**
 * Stops all interactions and clears the selection.
 */
Graph.prototype.reset = function () {
  if (this.isEditing()) {
    this.stopEditing(true);
  }

  this.escape();

  if (!this.isSelectionEmpty()) {
    this.clearSelection();
  }
};

/**
 * Overridden to limit zoom to 1% - 16.000%.
 */
Graph.prototype.zoom = function (factor, center) {
  factor = Math.max(0.01, Math.min(this.view.scale * factor, 160)) / this.view.scale;
  mxGraph.prototype.zoom.apply(this, arguments);
};

/**
 * Function: zoomIn
 *
 * Zooms into the graph by <zoomFactor>.
 */
Graph.prototype.zoomIn = function () {
  //// Switches to 1% zoom steps below 15%
  if (this.view.scale < 0.15) {
    this.zoom((this.view.scale + 0.01) / this.view.scale);
  } else {
    //// Uses to 5% zoom steps for better grid rendering in webkit
    //// and to avoid rounding errors for zoom steps
    this.zoom(
      Math.round(this.view.scale * this.zoomFactor * 20) / 20 / this.view.scale
    );
  }
};

/**
 * Function: zoomOut
 *
 * Zooms out of the graph by <zoomFactor>.
 */
Graph.prototype.zoomOut = function () {
  //// Switches to 1% zoom steps below 15%
  if (this.view.scale <= 0.15) {
    this.zoom((this.view.scale - 0.01) / this.view.scale);
  } else {
    //// Uses to 5% zoom steps for better grid rendering in webkit
    //// and to avoid rounding errors for zoom steps
    this.zoom(
      Math.round(this.view.scale * (1 / this.zoomFactor) * 20) /
      20 /
      this.view.scale
    );
  }
};

/**
 * Turns the given string into an array.
 */
Graph.prototype.stringToBytes = function (str) {
  return Graph.stringToBytes(str);
};

/**
 * Turns the given array into a string.
 */
Graph.prototype.bytesToString = function (arr) {
  return Graph.bytesToString(arr);
};

/**
 * Returns a base64 encoded version of the compressed outer XML of the given node.
 */
Graph.prototype.compressNode = function (node) {
  return Graph.compressNode(node);
};

/**
 * Returns a base64 encoded version of the compressed string.
 */
Graph.prototype.compress = function (data, deflate) {
  return Graph.compress(data, deflate);
};

/**
 * Returns a decompressed version of the base64 encoded string.
 */
Graph.prototype.decompress = function (data, inflate) {
  return Graph.decompress(data, inflate);
};

/**
 * Redirects to Graph.zapGremlins.
 */
Graph.prototype.zapGremlins = function (text) {
  return Graph.zapGremlins(text);
};

Graph.prototype.cellMenu = null;

Graph.prototype.interceptor = {
  set: function (recObj, key, value) {
    console.log(key, 'is changed to', value); //// name is changed to lisi
    recObj[key] = value;
  }
};

Graph.prototype.selectedElementProxy = null;

(function () {

  /**
   * Forces repaint if routed points have changed.
   */
  const mxCellRendererIsShapeInvalid = mxCellRenderer.prototype.isShapeInvalid;

  mxCellRenderer.prototype.isShapeInvalid = function (state, shape) {
    return (
      mxCellRendererIsShapeInvalid.apply(this, arguments) ||
      (state.routedPoints != null &&
        shape.routedPoints != null &&
        !mxUtils.equalPoints(shape.routedPoints, state.routedPoints))
    );
  };

  /**
   * Overrides painting the actual shape for taking into account jump style.
   */
  const mxConnectorPaintLine = mxConnector.prototype.paintLine;

  mxConnector.prototype.paintLine = function (c, absPts, rounded) {
    //// Required for checking dirty state
    this.routedPoints = this.state != null ? this.state.routedPoints : null;

    if (
      this.outline ||
      this.state == null ||
      this.style == null ||
      this.state.routedPoints == null ||
      this.state.routedPoints.length == 0
    ) {
      mxConnectorPaintLine.apply(this, arguments);
    } else {
      const arcSize =
        mxUtils.getValue(
          this.style,
          mxConstants.STYLE_ARCSIZE,
          mxConstants.LINE_ARCSIZE
        ) / 2;
      const size =
        (parseInt(
          mxUtils.getValue(this.style, "jumpSize", Graph.defaultJumpSize)
        ) -
          2) /
        2 +
        this.strokewidth;
      const style = mxUtils.getValue(this.style, "jumpStyle", "none");
      var f = Editor.jumpSizeRatio;
      let moveTo = true;
      let last = null;
      let len = null;
      let pts = [];
      let n = null;
      c.begin();

      for (let i = 0; i < this.state.routedPoints.length; i++) {
        const rpt = this.state.routedPoints[i];
        let pt = new mxPoint(rpt.x / this.scale, rpt.y / this.scale);

        //// Takes first and last point from passed-in array
        if (i == 0) {
          pt = absPts[0];
        } else if (i == this.state.routedPoints.length - 1) {
          pt = absPts[absPts.length - 1];
        }

        let done = false;

        //// Type 1 is an intersection
        if (last != null && rpt.type == 1) {
          //// Checks if next/previous points are too close
          const next = this.state.routedPoints[i + 1];
          var dx = next.x / this.scale - pt.x;
          var dy = next.y / this.scale - pt.y;
          var dist = dx * dx + dy * dy;

          if (n == null) {
            n = new mxPoint(pt.x - last.x, pt.y - last.y);
            len = Math.sqrt(n.x * n.x + n.y * n.y);

            if (len > 0) {
              n.x = (n.x * size) / len;
              n.y = (n.y * size) / len;
            } else {
              n = null;
            }
          }

          if (dist > size * size && len > 0) {
            var dx = last.x - pt.x;
            var dy = last.y - pt.y;
            var dist = dx * dx + dy * dy;

            if (dist > size * size) {
              const p0 = new mxPoint(pt.x - n.x, pt.y - n.y);
              const p1 = new mxPoint(pt.x + n.x, pt.y + n.y);
              pts.push(p0);

              this.addPoints(c, pts, rounded, arcSize, false, null, moveTo);

              var f =
                Math.round(n.x) < 0 ||
                (Math.round(n.x) == 0 && Math.round(n.y) <= 0)
                  ? 1
                  : -1;
              moveTo = false;

              if (style == "sharp") {
                c.lineTo(p0.x - n.y * f, p0.y + n.x * f);
                c.lineTo(p1.x - n.y * f, p1.y + n.x * f);
                c.lineTo(p1.x, p1.y);
              } else if (style == "arc") {
                f *= 1.3;
                c.curveTo(
                  p0.x - n.y * f,
                  p0.y + n.x * f,
                  p1.x - n.y * f,
                  p1.y + n.x * f,
                  p1.x,
                  p1.y
                );
              } else {
                c.moveTo(p1.x, p1.y);
                moveTo = true;
              }

              pts = [p1];
              done = true;
            }
          }
        } else {
          n = null;
        }

        if (!done) {
          pts.push(pt);
          last = pt;
        }
      }

      this.addPoints(c, pts, rounded, arcSize, false, null, moveTo);
      c.stroke();
    }
  };

  /**
   * Adds support for placeholders in text elements of shapes.
   */
  const mxStencilEvaluateTextAttribute =
    mxStencil.prototype.evaluateTextAttribute;

  mxStencil.prototype.evaluateTextAttribute = function (node, attribute, shape) {
    let result = mxStencilEvaluateTextAttribute.apply(this, arguments);
    const placeholders = node.getAttribute("placeholders");

    if (placeholders == "1" && shape.state != null) {
      result = shape.state.view.graph.replacePlaceholders(
        shape.state.cell,
        result
      );
    }

    return result;
  };

  /**
   * Adds custom stencils defined via shape=stencil(value) style. The value is a base64 encoded, compressed and
   * URL encoded XML definition of the shape according to the stencil definition language of mxGraph.
   *
   * Needs to be in this file to make sure its part of the embed client code. Also the check for ZLib is
   * different than for the Editor code.
   */
  const mxCellRendererCreateShape = mxCellRenderer.prototype.createShape;
  mxCellRenderer.prototype.createShape = function (state) {
    if (state.style != null && typeof pako !== "undefined") {
      const shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);

      //// Extracts and decodes stencil XML if shape has the form shape=stencil(value)
      if (
        shape != null &&
        typeof shape === "string" &&
        shape.substring(0, 8) == "stencil("
      ) {
        try {
          const stencil = shape.substring(8, shape.length - 1);
          const doc = mxUtils.parseXml(Graph.decompress(stencil));

          return new mxShape(new mxStencil(doc.documentElement));
        } catch (e) {
          if (window.console != null) {
            console.log("Error in shape: " + e);
          }
        }
      }
    }
    return mxCellRendererCreateShape.apply(this, arguments);
  };
})();
/**
 * Overrides stencil registry for dynamic loading of stencils.
 */
/**
 * Maps from library names to an array of Javascript filenames,
 * which are synchronously loaded. Currently only stencil files
 * (.xml) and JS files (.js) are supported.
 * IMPORTANT: For embedded diagrams to work entries must also
 * be added in EmbedServlet.java.
 */
mxStencilRegistry.libraries = {};

/**
 * Global switch to disable dynamic loading.
 */
mxStencilRegistry.dynamicLoading = true;

/**
 * Global switch to disable eval for JS (preload all JS instead).
 */
mxStencilRegistry.allowEval = true;

/**
 * Stores all package names that have been dynamically loaded.
 * Each package is only loaded once.
 */
mxStencilRegistry.packages = [];

//// Extends the default stencil registry to add dynamic loading
mxStencilRegistry.getStencil = function (name) {
  let result = mxStencilRegistry.stencils[name];

  if (
    result == null &&
    mxCellRenderer.defaultShapes[name] == null &&
    mxStencilRegistry.dynamicLoading
  ) {
    let basename = mxStencilRegistry.getBasenameForStencil(name);

    //// Loads stencil files and tries again
    if (basename != null) {
      const libs = mxStencilRegistry.libraries[basename];

      if (libs != null) {
        if (mxStencilRegistry.packages[basename] == null) {
          for (let i = 0; i < libs.length; i++) {
            const fname = libs[i];

            if (
              fname.toLowerCase().substring(fname.length - 4, fname.length) ==
              ".xml"
            ) {
              mxStencilRegistry.loadStencilSet(fname, null);
            } else if (
              fname.toLowerCase().substring(fname.length - 3, fname.length) ==
              ".js"
            ) {
              try {
                if (mxStencilRegistry.allowEval) {
                  const req = mxUtils.load(fname);

                  if (
                    req != null &&
                    req.getStatus() >= 200 &&
                    req.getStatus() <= 299
                  ) {
                    eval.call(window, req.getText());
                  }
                }
              } catch (e) {
                if (window.console != null) {
                  console.log("error in getStencil:", fname, e);
                }
              }
            } else {
              //// FIXME: This does not yet work as the loading is triggered after
              //// the shape was used in the graph, at which point the keys have
              //// typically been translated in the calling method.
              //// mxResources.add(fname);
            }
          }

          mxStencilRegistry.packages[basename] = 1;
        }
      } else {
        //// Replaces '_-_' with '_'
        basename = basename.replace("_-_", "_");
        mxStencilRegistry.loadStencilSet(
          STENCIL_PATH + "/" + basename + ".xml",
          null
        );
      }

      result = mxStencilRegistry.stencils[name];
    }
  }

  return result;
};

//// Returns the basename for the given stencil or null if no file must be
//// loaded to render the given stencil.
mxStencilRegistry.getBasenameForStencil = function (name) {
  let tmp = null;

  if (name != null && typeof name === "string") {
    const parts = name.split(".");

    if (parts.length > 0 && parts[0] == "mxgraph") {
      tmp = parts[1];

      for (let i = 2; i < parts.length - 1; i++) {
        tmp += "/" + parts[i];
      }
    }
  }

  return tmp;
};

//// Loads the given stencil set
mxStencilRegistry.loadStencilSet = function (
  stencilFile,
  postStencilLoad,
  force,
  async
) {
  force = force != null ? force : false;

  //// Uses additional cache for detecting previous load attempts
  let xmlDoc = mxStencilRegistry.packages[stencilFile];

  if (force || xmlDoc == null) {
    let install = false;

    if (xmlDoc == null) {
      try {
        if (async) {
          mxStencilRegistry.loadStencil(
            stencilFile,
            mxUtils.bind(this, function (xmlDoc2) {
              if (xmlDoc2 != null && xmlDoc2.documentElement != null) {
                mxStencilRegistry.packages[stencilFile] = xmlDoc2;
                install = true;
                mxStencilRegistry.parseStencilSet(
                  xmlDoc2.documentElement,
                  postStencilLoad,
                  install
                );
              }
            })
          );

          return;
        } else {
          xmlDoc = mxStencilRegistry.loadStencil(stencilFile);
          mxStencilRegistry.packages[stencilFile] = xmlDoc;
          install = true;
        }
      } catch (e) {
        if (window.console != null) {
          console.log("error in loadStencilSet:", stencilFile, e);
        }
      }
    }

    if (xmlDoc != null && xmlDoc.documentElement != null) {
      mxStencilRegistry.parseStencilSet(
        xmlDoc.documentElement,
        postStencilLoad,
        install
      );
    }
  }
};

//// Loads the given stencil XML file.
mxStencilRegistry.loadStencil = function (filename, fn) {
  if (fn != null) {
    const req = mxUtils.get(
      filename,
      mxUtils.bind(this, function (req) {
        fn(
          req.getStatus() >= 200 && req.getStatus() <= 299 ? req.getXml() : null
        );
      })
    );
  } else {
    return mxUtils.load(filename).getXml();
  }
};

//// Takes array of strings
mxStencilRegistry.parseStencilSets = function (stencils) {
  for (let i = 0; i < stencils.length; i++) {
    mxStencilRegistry.parseStencilSet(
      mxUtils.parseXml(stencils[i]).documentElement
    );
  }
};

//// Parses the given stencil set
mxStencilRegistry.parseStencilSet = function (root, postStencilLoad, install) {
  if (root.nodeName == "stencils") {
    let shapes = root.firstChild;

    while (shapes != null) {
      if (shapes.nodeName == "shapes") {
        mxStencilRegistry.parseStencilSet(shapes, postStencilLoad, install);
      }

      shapes = shapes.nextSibling;
    }
  } else {
    install = install != null ? install : true;
    let shape = root.firstChild;
    let packageName = "";
    let name = root.getAttribute("name");

    if (name != null) {
      packageName = name + ".";
    }

    while (shape != null) {
      if (shape.nodeType == mxConstants.NODETYPE_ELEMENT) {
        name = shape.getAttribute("name");

        if (name != null) {
          packageName = packageName.toLowerCase();
          const stencilName = name.replace(/ /g, "_");

          if (install) {
            mxStencilRegistry.addStencil(
              packageName + stencilName.toLowerCase(),
              new mxStencil(shape)
            );
          }

          if (postStencilLoad != null) {
            let w = shape.getAttribute("w");
            let h = shape.getAttribute("h");

            w = w == null ? 80 : parseInt(w, 10);
            h = h == null ? 80 : parseInt(h, 10);

            postStencilLoad(packageName, stencilName, name, w, h);
          }
        }
      }

      shape = shape.nextSibling;
    }
  }
};

/**
 * These overrides are only added if mxVertexHandler is defined (ie. not in embedded graph)
 */
if (typeof mxVertexHandler != "undefined") {
  (function () {
    //// Sets colors for handles
    mxConstants.HANDLE_FILLCOLOR = "#29b6f2";
    mxConstants.HANDLE_STROKECOLOR = "#0088cf";
    mxConstants.VERTEX_SELECTION_COLOR = "#a0a0a0"; 
    mxConstants.OUTLINE_COLOR = "#00a8ff";
    mxConstants.OUTLINE_HANDLE_FILLCOLOR = "#99ccff";
    mxConstants.OUTLINE_HANDLE_STROKECOLOR = "#00a8ff";
    mxConstants.CONNECT_HANDLE_FILLCOLOR = "#cee7ff";
    mxConstants.EDGE_SELECTION_COLOR = "#a0a0a0";
    mxConstants.DEFAULT_VALID_COLOR = "#00a8ff";
    mxConstants.LABEL_HANDLE_FILLCOLOR = "#cee7ff";
    mxConstants.GUIDE_COLOR = "#0088cf";
    mxConstants.HIGHLIGHT_OPACITY = 30;
    mxConstants.HIGHLIGHT_SIZE = 4;

    //// Enables snapping to off-grid terminals for edge waypoints

    //// Enables fading of rubberband

    //// Alt-move disables guides
    mxGuide.prototype.isEnabledForEvent = function (evt) {
      return !mxEvent.isAltDown(evt);
    };

    //// Overrides highlight shape for connection points
    mxConstraintHandler.prototype.createHighlightShape = function () {
      const hl = new mxEllipse(null, this.highlightColor, this.highlightColor, 0);
      hl.opacity = mxConstants.HIGHLIGHT_OPACITY;

      return hl;
    };

    /**
     *
     */
    Graph.prototype.defaultVertexStyle = {};

    /**
     * Contains the default style for edges.
     */
    Graph.prototype.defaultEdgeStyle = {
      edgeStyle: "orthogonalEdgeStyle",
      rounded: "0",
      jettySize: "auto",
      orthogonalLoop: "1"
    };

    /**
     * Hook for subclassers.
     */
    Graph.prototype.getPagePadding = function () {
      return new mxPoint(0, 0);
    };

    /**
     * Loads the stylesheet for this graph.
     */
    Graph.prototype.loadStylesheet = function () {
      const node =
        this.themes != null
          ? this.themes[this.defaultThemeName]
          : !mxStyleRegistry.dynamicLoading
            ? null
            : mxUtils.load(STYLE_PATH + "/default.xml").getDocumentElement();

      if (node != null) {
        const dec = new mxCodec(node.ownerDocument);
        dec.decode(node, this.getStylesheet());
      }
    };

    /**
     * 导入的弹层模块
     */
    Graph.prototype.importGraphModel = function (node, dx, dy, crop) {
      dx = dx != null ? dx : 0;
      dy = dy != null ? dy : 0;

      const codec = new mxCodec(node.ownerDocument);
      const tempModel = new mxGraphModel();
      codec.decode(node, tempModel);
      let cells = [];

      //// Clones cells to remove invalid edges
      let layers = tempModel.getChildren(
        this.cloneCell(tempModel.root, this.isCloneInvalidEdges())
      );

      if (layers != null) {
        //// Uses copy as layers are removed from array inside loop
        layers = layers.slice();

        this.model.beginUpdate();
        try {
          //// Merges into unlocked current layer if one layer is pasted
          if (
            layers.length == 1 &&
            !this.isCellLocked(this.getDefaultParent())
          ) {
            cells = this.moveCells(
              tempModel.getChildren(layers[0]),
              dx,
              dy,
              false,
              this.getDefaultParent()
            );
          } else {
            for (let i = 0; i < layers.length; i++) {
              cells = cells.concat(
                this.model.getChildren(
                  this.moveCells(
                    [layers[i]],
                    dx,
                    dy,
                    false,
                    this.model.getRoot()
                  )[0]
                )
              );
            }
          }

          if (crop) {
            if (this.isGridEnabled()) {
              dx = this.snap(dx);
              dy = this.snap(dy);
            }

            const bounds = this.getBoundingBoxFromGeometry(cells, true);

            if (bounds != null) {
              this.moveCells(cells, dx - bounds.x, dy - bounds.y);
            }
          }
        } finally {
          this.model.endUpdate();
        }
      }

      return cells;
    };

    /**
     * Overrides method to provide connection constraints for shapes.
     */
    Graph.prototype.getAllConnectionConstraints = function (terminal, source) {
      if (terminal != null) {
        let constraints = mxUtils.getValue(terminal.style, "points", null);

        if (constraints != null) {
          //// Requires an array of arrays with x, y (0..1), an optional
          //// [perimeter (0 or 1), dx, and dy] eg. points=[[0,0,1,-10,10],[0,1,0],[1,1]]
          const result = [];

          try {
            const c = JSON.parse(constraints);

            for (let i = 0; i < c.length; i++) {
              var tmp = c[i];
              result.push(
                new mxConnectionConstraint(
                  new mxPoint(tmp[0], tmp[1]),
                  tmp.length > 2 ? tmp[2] != "0" : true,
                  null,
                  tmp.length > 3 ? tmp[3] : 0,
                  tmp.length > 4 ? tmp[4] : 0
                )
              );
            }
          } catch (e) {
            //// ignore
          }

          return result;
        } else if (terminal.shape != null && terminal.shape.bounds != null) {
          const dir = terminal.shape.direction;
          const bounds = terminal.shape.bounds;
          const scale = terminal.shape.scale;
          let w = bounds.width / scale;
          let h = bounds.height / scale;

          if (
            dir == mxConstants.DIRECTION_NORTH ||
            dir == mxConstants.DIRECTION_SOUTH
          ) {
            var tmp = w;
            w = h;
            h = tmp;
          }

          constraints = terminal.shape.getConstraints(terminal.style, w, h);

          if (constraints != null) {
            return constraints;
          } else if (
            terminal.shape.stencil != null &&
            terminal.shape.stencil.constraints != null
          ) {
            return terminal.shape.stencil.constraints;
          } else if (terminal.shape.constraints != null) {
            return terminal.shape.constraints;
          }
        }
      }

      return null;
    };

    /**
     * Inverts the elbow edge style without removing existing styles.
     */
    Graph.prototype.flipEdge = function (edge) {
      if (edge != null) {
        const state = this.view.getState(edge);
        const style = state != null ? state.style : this.getCellStyle(edge);

        if (style != null) {
          const elbow = mxUtils.getValue(
            style,
            mxConstants.STYLE_ELBOW,
            mxConstants.ELBOW_HORIZONTAL
          );
          const value =
            elbow == mxConstants.ELBOW_HORIZONTAL
              ? mxConstants.ELBOW_VERTICAL
              : mxConstants.ELBOW_HORIZONTAL;
          this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
        }
      }
    };

    /**
     * Disables drill-down for non-swimlanes.
     */
    Graph.prototype.isValidRoot = function (cell) {
      //// Counts non-relative children
      const childCount = this.model.getChildCount(cell);
      let realChildCount = 0;

      for (let i = 0; i < childCount; i++) {
        const child = this.model.getChildAt(cell, i);

        if (this.model.isVertex(child)) {
          const geometry = this.getCellGeometry(child);

          if (geometry != null && !geometry.relative) {
            realChildCount++;
          }
        }
      }

      return realChildCount > 0 || this.isContainer(cell);
    };

    /**
     * Overrides createGroupCell to set the group style for new groups to 'group'.
     */
    Graph.prototype.createGroupCell = function () {
      const group = mxGraph.prototype.createGroupCell.apply(this, arguments);
      group.setStyle("group");
      return group;
    };

    /**
     * Disables extending parents with stack layouts on add
     */
    Graph.prototype.isExtendParentsOnAdd = function (cell) {
      let result = mxGraph.prototype.isExtendParentsOnAdd.apply(
        this,
        arguments
      );

      if (result && cell != null && this.layoutManager != null) {
        const parent = this.model.getParent(cell);

        if (parent != null) {
          const layout = this.layoutManager.getLayout(parent);

          if (layout != null && layout.constructor == mxStackLayout) {
            result = false;
          }
        }
      }
      return result;
    };

    /**
     * Overrides autosize to add a border.
     */
    Graph.prototype.getPreferredSizeForCell = function (cell) {
      const result = mxGraph.prototype.getPreferredSizeForCell.apply(
        this,
        arguments
      );
      //// Adds buffer
      if (result != null) {
        result.width += 10;
        result.height += 4;
        if (this.gridEnabled) {
          result.width = this.snap(result.width);
          result.height = this.snap(result.height);
        }
      }
      return result;
    };

    /**
     *
     * overrides update to autosize 修改图形自适应函数
     */
    //// Graph.prototype.cellSizeUpdated = function(cell, ignoreChildren){
    ////   //// mxGraph.prototype.cellSizeUpdated.apply(this,arguments);
    ////   var model = this.getModel();
    ////   if (cell != null)
    ////   {
    ////     model.beginUpdate();
    ////     try
    ////     {
    ////       var size = this.getPreferredSizeForCell(cell);
    ////       var geo = this.model.getGeometry(cell);
    ////
    ////       if (size != null && geo != null && size.width > geo.width)
    ////       {
    ////         var collapsed = this.isCellCollapsed(cell);
    ////         geo = geo.clone();
    ////
    ////         if (this.isSwimlane(cell))
    ////         {
    ////           var style = this.getCellStyle(cell);
    ////           var cellStyle = this.model.getStyle(cell);
    ////
    ////           if (cellStyle == null)
    ////           {
    ////             cellStyle = '';
    ////           }
    ////
    ////           if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
    ////           {
    ////             cellStyle = mxUtils.setStyle(cellStyle,
    ////               mxConstants.STYLE_STARTSIZE, size.height + 8);
    ////
    ////             if (collapsed)
    ////             {
    ////               geo.height = size.height + 8;
    ////             }
    ////
    ////             geo.width = size.width;
    ////           }
    ////           else
    ////           {
    ////             cellStyle = mxUtils.setStyle(cellStyle,
    ////               mxConstants.STYLE_STARTSIZE, size.width + 8);
    ////
    ////             if (collapsed)
    ////             {
    ////               geo.width = size.width + 8;
    ////             }
    ////
    ////             geo.height = size.height;
    ////           }
    ////
    ////           this.model.setStyle(cell, cellStyle);
    ////         }
    ////         else
    ////         {
    ////           var state = this.view.createState(cell);
    ////           var align = (state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER);
    ////
    ////           if (align == mxConstants.ALIGN_RIGHT)
    ////           {
    ////             geo.x += geo.width - size.width;
    ////           }
    ////           else if (align == mxConstants.ALIGN_CENTER)
    ////           {
    ////             //// 修改坐标位置
    ////             //// geo.x += Math.round((geo.width - size.width) / 2);
    ////             //// geo.x = 0;
    ////           }
    ////
    ////           var valign = this.getVerticalAlign(state);
    ////
    ////           if (valign == mxConstants.ALIGN_BOTTOM)
    ////           {
    ////             geo.y += geo.height - size.height;
    ////           }
    ////           else if (valign == mxConstants.ALIGN_MIDDLE)
    ////           {
    ////             geo.y += Math.round((geo.height - size.height) / 2);
    ////           }
    ////
    ////           geo.width = size.width;
    ////           geo.height = size.height;
    ////         }
    ////
    ////         if (!ignoreChildren && !collapsed)
    ////         {
    ////           var bounds = this.view.getBounds(this.model.getChildren(cell));
    ////
    ////           if (bounds != null)
    ////           {
    ////             var tr = this.view.translate;
    ////             var scale = this.view.scale;
    ////
    ////             var width = (bounds.x + bounds.width) / scale - geo.x - tr.x;
    ////             var height = (bounds.y + bounds.height) / scale - geo.y - tr.y;
    ////
    ////             geo.width = Math.max(geo.width, width);
    ////             geo.height = Math.max(geo.height, height);
    ////           }
    ////         }
    ////
    ////         this.cellsResized([cell], [geo], false);
    ////       }
    ////     }
    ////     finally
    ////     {
    ////       model.endUpdate();
    ////     }
    ////   }
    //// };

    /**
     * Turns the given cells and returns the changed cells.
     */
    Graph.prototype.turnShapes = function (cells) {
      const model = this.getModel();
      const select = [];
      model.beginUpdate();
      try {
        for (let i = 0; i < cells.length; i++) {
          const cell = cells[i];

          if (model.isEdge(cell)) {
            const src = model.getTerminal(cell, true);
            const trg = model.getTerminal(cell, false);

            model.setTerminal(cell, trg, true);
            model.setTerminal(cell, src, false);

            var geo = model.getGeometry(cell);

            if (geo != null) {
              geo = geo.clone();

              if (geo.points != null) {
                geo.points.reverse();
              }

              const sp = geo.getTerminalPoint(true);
              const tp = geo.getTerminalPoint(false);

              geo.setTerminalPoint(sp, false);
              geo.setTerminalPoint(tp, true);
              model.setGeometry(cell, geo);

              //// Inverts constraints
              const edgeState = this.view.getState(cell);
              const sourceState = this.view.getState(src);
              const targetState = this.view.getState(trg);

              if (edgeState != null) {
                const sc =
                  sourceState != null
                    ? this.getConnectionConstraint(edgeState, sourceState, true)
                    : null;
                const tc =
                  targetState != null
                    ? this.getConnectionConstraint(
                      edgeState,
                      targetState,
                      false
                    )
                    : null;

                this.setConnectionConstraint(cell, src, true, tc);
                this.setConnectionConstraint(cell, trg, false, sc);
              }

              select.push(cell);
            }
          } else if (model.isVertex(cell)) {
            var geo = this.getCellGeometry(cell);

            if (geo != null) {
              //// Rotates the size and position in the geometry
              geo = geo.clone();
              geo.x += geo.width / 2 - geo.height / 2;
              geo.y += geo.height / 2 - geo.width / 2;
              const tmp = geo.width;
              geo.width = geo.height;
              geo.height = tmp;
              model.setGeometry(cell, geo);

              //// Reads the current direction and advances by 90 degrees
              const state = this.view.getState(cell);

              if (state != null) {
                let dir =
                  state.style[mxConstants.STYLE_DIRECTION] ||
                  "east"; /* default*/

                if (dir == "east") {
                  dir = "south";
                } else if (dir == "south") {
                  dir = "west";
                } else if (dir == "west") {
                  dir = "north";
                } else if (dir == "north") {
                  dir = "east";
                }
                this.setCellStyles(mxConstants.STYLE_DIRECTION, dir, [cell]);
              }
              select.push(cell);
            }
          }
        }
      } finally {
        model.endUpdate();
      }
      return select;
    };

    /**
     * Returns true if the given stencil contains any placeholder text.
     */
    Graph.prototype.stencilHasPlaceholders = function (stencil) {
      if (stencil != null && stencil.fgNode != null) {
        let node = stencil.fgNode.firstChild;
        while (node != null) {
          if (
            node.nodeName == "text" &&
            node.getAttribute("placeholders") == "1"
          ) {
            return true;
          }
          node = node.nextSibling;
        }
      }
      return false;
    };

    /**
     * Updates the child cells with placeholders if metadata of a cell has changed.
     */
    Graph.prototype.processChange = function (change) {
      mxGraph.prototype.processChange.apply(this, arguments);
      if (
        change.constructor.toString().indexOf("mxValueChange") > -1 &&
        change.cell != null &&
        change.cell.value != null &&
        typeof change.cell.value == "object"
      ) {
        //// Invalidates all descendants with placeholders
        const desc = this.model.getDescendants(change.cell);

        //// LATER: Check if only label or tooltip have changed
        if (desc.length > 0) {
          for (let i = 0; i < desc.length; i++) {
            const state = this.view.getState(desc[i]);

            if (
              state != null &&
              state.shape != null &&
              state.shape.stencil != null &&
              this.stencilHasPlaceholders(state.shape.stencil)
            ) {
              this.removeStateForCell(desc[i]);
            } else if (this.isReplacePlaceholders(desc[i])) {
              this.view.invalidate(desc[i], false, false);
            }
          }
        }
      }
    };

    /**
     * Replaces the given element with a span.
     */
    Graph.prototype.replaceElement = function (elt, tagName) {
      console.log("111:", elt, tagName);
      const span = elt.ownerDocument.createElement(
        tagName != null ? tagName : "span"
      );
      const attributes = Array.prototype.slice.call(elt.attributes);
      while ((attr = attributes.pop())) {
        span.setAttribute(attr.nodeName, attr.nodeValue);
      }
      span.innerHTML = elt.innerHTML;
      elt.parentNode.replaceChild(span, elt);
    };

    /**
     *
     */
    Graph.prototype.processElements = function (elt, fn) {
      const elts = elt.getElementsByTagName("*");
      for (let i = 0; i < elts.length; i++) {
        fn(elts[i]);
      }
    };

    /**
     * Handles label changes for XML user objects.
     */
    Graph.prototype.updateLabelElements = function (cells, fn, tagName) {
      cells = cells != null ? cells : this.getSelectionCells();
      const div = document.createElement("div");
      for (let i = 0; i < cells.length; i++) {
        //// Changes font tags inside HTML labels
        if (this.isHtmlLabel(cells[i])) {
          const label = this.convertValueToString(cells[i]);
          if (label != null && label.length > 0) {
            div.innerHTML = label;
            const elts = div.getElementsByTagName(
              tagName != null ? tagName : "*"
            );
            for (let j = 0; j < elts.length; j++) {
              fn(elts[j]);
            }

            if (div.innerHTML != label) {
              this.cellLabelChanged(cells[i], div.innerHTML);
            }
          }
        }
      }
    };
    /**
     * Handles label changes for XML user objects.
     */
    Graph.prototype.cellLabelChanged = function (cell, value, autoSize) {
      //// Removes all illegal control characters in user input
      value = Graph.zapGremlins(value);
      this.model.beginUpdate();
      try {
        if (cell.value != null) {
          if (typeof cell.value == "object") {
            if (
              this.isReplacePlaceholders(cell) &&
              cell.getAttribute("placeholder") != null
            ) {
              //// LATER: Handle delete, name change
              const name = cell.getAttribute("placeholder");
              let current = cell;
              while (current != null) {
                if (
                  current == this.model.getRoot() ||
                  (current.value != null &&
                    typeof current.value == "object" &&
                    current.hasAttribute(name))
                ) {
                  this.setAttributeForCell(current, name, value);
                  break;
                }
                current = this.model.getParent(current);
              }
            }
            const tmp = cell.value.cloneNode(true);
            tmp.setAttribute("label", value);
          }
        }
        mxGraph.prototype.cellLabelChanged.apply(this, arguments);
      } finally {
        this.model.endUpdate();
      }
    };
    /**
     * Removes transparent empty groups if all children are removed.
     */
    Graph.prototype.cellsRemoved = function (cells) {
      if (cells != null) {
        const dict = new mxDictionary();
        for (var i = 0; i < cells.length; i++) {
          dict.put(cells[i], true);
        }
        //// LATER: Recurse up the cell hierarchy
        const parents = [];
        for (var i = 0; i < cells.length; i++) {
          const parent = this.model.getParent(cells[i]);
          if (parent != null && !dict.get(parent)) {
            dict.put(parent, true);
            parents.push(parent);
          }
        }

        for (var i = 0; i < parents.length; i++) {
          const state = this.view.getState(parents[i]);

          if (
            state != null &&
            (this.model.isEdge(state.cell) ||
              this.model.isVertex(state.cell)) &&
            this.isCellDeletable(state.cell)
          ) {
            const stroke = mxUtils.getValue(
              state.style,
              mxConstants.STYLE_STROKECOLOR,
              mxConstants.NONE
            );
            const fill = mxUtils.getValue(
              state.style,
              mxConstants.STYLE_FILLCOLOR,
              mxConstants.NONE
            );

            if (stroke == mxConstants.NONE && fill == mxConstants.NONE) {
              let allChildren = true;

              for (
                let j = 0;
                j < this.model.getChildCount(state.cell) && allChildren;
                j++
              ) {
                if (!dict.get(this.model.getChildAt(state.cell, j))) {
                  allChildren = false;
                }
              }

              if (allChildren) {
                cells.push(state.cell);
              }
            }
          }
        }
      }
      mxGraph.prototype.cellsRemoved.apply(this, arguments);
    };

    /**
     * Overrides ungroup to check if group should be removed.
     */
    Graph.prototype.removeCellsAfterUngroup = function (cells) {
      const cellsToRemove = [];
      for (let i = 0; i < cells.length; i++) {
        if (this.isCellDeletable(cells[i])) {
          const state = this.view.getState(cells[i]);

          if (state != null) {
            const stroke = mxUtils.getValue(
              state.style,
              mxConstants.STYLE_STROKECOLOR,
              mxConstants.NONE
            );
            const fill = mxUtils.getValue(
              state.style,
              mxConstants.STYLE_FILLCOLOR,
              mxConstants.NONE
            );

            if (stroke == mxConstants.NONE && fill == mxConstants.NONE) {
              cellsToRemove.push(cells[i]);
            }
          }
        }
      }

      cells = cellsToRemove;

      mxGraph.prototype.removeCellsAfterUngroup.apply(this, arguments);
    };

    /**
     * Sets the link for the given cell.
     */
    Graph.prototype.setLinkForCell = function (cell, link) {
      this.setAttributeForCell(cell, "link", link);
    };

    /**
     * Sets the link for the given cell.
     */
    Graph.prototype.setTooltipForCell = function (cell, link) {
      this.setAttributeForCell(cell, "tooltip", link);
    };

    /**
     * Returns the cells in the model (or given array) that have all of the
     * given tags in their tags property.
     */
    Graph.prototype.getAttributeForCell = function (
      cell,
      attributeName,
      defaultValue
    ) {
      return cell.value != null && typeof cell.value === "object"
        ? cell.value.getAttribute(attributeName) || defaultValue
        : defaultValue;
    };

    /**
     * Sets the link for the given cell.
     */
    Graph.prototype.setAttributeForCell = function (
      cell,
      attributeName,
      attributeValue
    ) {
      let value = null;

      if (cell.value != null && typeof cell.value == "object") {
        value = cell.value.cloneNode(true);
      } else {
        const doc = mxUtils.createXmlDocument();

        value = doc.createElement("UserObject");
        value.setAttribute("label", cell.value || "");
      }

      if (attributeValue != null) {
        value.setAttribute(attributeName, attributeValue);
      } else {
        value.removeAttribute(attributeName);
      }

      this.model.setValue(cell, value);
    };

    /**
     * Overridden to stop moving edge labels between cells.
     */
    Graph.prototype.getDropTarget = function (cells, evt, cell, clone) {
      //// todo dragstart
      const model = this.getModel();

      //// Disables drop into group if alt is pressed
      if (mxEvent.isAltDown(evt)) {
        return null;
      }

      //// Disables dragging edge labels out of edges
      for (let i = 0; i < cells.length; i++) {
        if (this.model.isEdge(this.model.getParent(cells[i]))) {
          return null;
        }
      }
      const target = mxGraph.prototype.getDropTarget.apply(this, arguments);
      //// console.log(target);

      return target;
    };

    /**
     * Overrides double click handling to avoid accidental inserts of new labels in dblClick below.
     */
    Graph.prototype.click = function (me) {
      mxGraph.prototype.click.call(this, me);
      //// Stores state and source for checking in dblClick
      this.firstClickState = me.getState();
      this.firstClickSource = me.getSource();
    };

    /**
     * Overrides double click handling to add the tolerance and inserting text.
     */
    Graph.prototype.dblClick = function (evt, cell) {
      //// 图片的名称的描述文字，双击不可修改，只能子啊弹层里面修改
      if (cell && cell.data_id) {
        return false;
      }

      if (this.isEnabled()) {
        const pt = mxUtils.convertPoint(
          this.container,
          mxEvent.getClientX(evt),
          mxEvent.getClientY(evt)
        );

        //// Automatically adds new child cells to edges on double click
        if (evt != null && !this.model.isVertex(cell)) {
          const state = this.model.isEdge(cell) ? this.view.getState(cell) : null;
          const src = mxEvent.getSource(evt);

          if (
            this.firstClickState == state &&
            this.firstClickSource == src &&
            (state == null ||
              state.text == null ||
              state.text.node == null ||
              state.text.boundingBox == null ||
              (!mxUtils.contains(state.text.boundingBox, pt.x, pt.y) &&
                !mxUtils.isAncestorNode(
                  state.text.node,
                  mxEvent.getSource(evt)
                ))) &&
            ((state == null && !this.isCellLocked(this.getDefaultParent())) ||
              (state != null && !this.isCellLocked(state.cell))) &&
            (state != null ||
              (mxClient.IS_VML && src == this.view.getCanvas()) ||
              (mxClient.IS_SVG && src == this.view.getCanvas().ownerSVGElement))
          ) {
            //// cell = this.addText(pt.x, pt.y, state);
          }
        }

        mxGraph.prototype.dblClick.call(this, evt, cell);
      }
    };

    /**
     * Returns a point that specifies the location for inserting cells.
     */
    Graph.prototype.getInsertPoint = function () {
      const gs = this.getGridSize();
      let dx =
        this.container.scrollLeft / this.view.scale - this.view.translate.x;
      let dy =
        this.container.scrollTop / this.view.scale - this.view.translate.y;

      if (this.pageVisible) {
        const layout = this.getPageLayout();
        const page = this.getPageSize();
        dx = Math.max(dx, layout.x * page.width);
        dy = Math.max(dy, layout.y * page.height);
      }

      return new mxPoint(this.snap(dx + gs), this.snap(dy + gs));
    };

    /**
     *
     */
    Graph.prototype.getFreeInsertPoint = function () {
      const view = this.view;
      const bds = this.getGraphBounds();
      const pt = this.getInsertPoint();

      //// Places at same x-coord and 2 grid sizes below existing graph
      const x = this.snap(
        Math.round(
          Math.max(
            pt.x,
            bds.x / view.scale -
            view.translate.x +
            (bds.width == 0 ? 2 * this.gridSize : 0)
          )
        )
      );
      const y = this.snap(
        Math.round(
          Math.max(
            pt.y,
            (bds.y + bds.height) / view.scale -
            view.translate.y +
            2 * this.gridSize
          )
        )
      );

      return new mxPoint(x, y);
    };

    /**
     * Hook for subclassers to return true if the current insert point was defined
     * using a mouse hover event.
     */
    Graph.prototype.isMouseInsertPoint = function () {
      return true;
    };

    /**
     * Adds a new label at the given position and returns the new cell. State is
     * an optional edge state to be used as the parent for the label. Vertices
     * are not allowed currently as states.
     */
    Graph.prototype.addText = function (x, y, state) {
      //// Creates a new edge label with a predefined text
      const label = new mxCell();
      label.value = "Text";
      label.style = "text;html=1;resizable=0;points=[];";
      label.geometry = new mxGeometry(0, 0, 0, 0);
      label.vertex = true;

      if (state != null) {
        label.style +=
          "align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;";
        label.geometry.relative = true;
        label.connectable = false;

        //// Resets the relative location stored inside the geometry
        let pt2 = this.view.getRelativePoint(state, x, y);
        label.geometry.x = Math.round(pt2.x * 10000) / 10000;
        label.geometry.y = Math.round(pt2.y);

        //// Resets the offset inside the geometry to find the offset from the resulting point
        label.geometry.offset = new mxPoint(0, 0);
        pt2 = this.view.getPoint(state, label.geometry);

        const scale = this.view.scale;
        label.geometry.offset = new mxPoint(
          Math.round((x - pt2.x) / scale),
          Math.round((y - pt2.y) / scale)
        );
      } else {
        label.style += "autosize=1;align=left;verticalAlign=top;spacingTop=-4;";

        const tr = this.view.translate;
        label.geometry.width = 40;
        label.geometry.height = 20;
        label.geometry.x = Math.round(x / this.view.scale) - tr.x;
        label.geometry.y = Math.round(y / this.view.scale) - tr.y;
      }

      this.getModel().beginUpdate();
      try {
        this.addCells([label], state != null ? state.cell : null);
        this.fireEvent(new mxEventObject("textInserted", "cells", [label]));
        //// Updates size of text after possible change of style via event
        this.autoSizeCell(label);
      } finally {
        this.getModel().endUpdate();
      }

      return label;
    };
    //// 绑定设备的 text 名称和描述 显示
    Graph.prototype.addTextNode = function (evt, cell, deviceInfo) {
      //// console.log('绑定设备cell:', cell);
      if (this.isEnabled()) {
        const pt = mxUtils.convertPoint(
          this.container,
          mxEvent.getClientX(evt) + deviceInfo.x,
          mxEvent.getClientY(evt) + deviceInfo.y
        );
        //// console.log('绑定设备pt:', pt, deviceInfo);
        ////   var state = (this.model.isEdge(cell)) ? this.view.getState(cell) : null;
        //// //// Automatically adds new child cells to edges on double click
        //// if (evt != null && !this.model.isVertex(cell)) {
        ////   var src = mxEvent.getSource(evt);

        ////   if ((this.firstClickState == state && this.firstClickSource == src) &&
        ////     (state == null || (state.text == null || state.text.node == null ||
        ////       state.text.boundingBox == null || (!mxUtils.contains(state.text.boundingBox,
        ////         pt.x, pt.y) && !mxUtils.isAncestorNode(state.text.node, mxEvent.getSource(evt))))) &&
        ////     ((state == null && !this.isCellLocked(this.getDefaultParent())) ||
        ////       (state != null && !this.isCellLocked(state.cell))) &&
        ////     (state != null || (mxClient.IS_VML && src == this.view.getCanvas()) ||
        ////       (mxClient.IS_SVG && src == this.view.getCanvas().ownerSVGElement))) {
        ////     cell = this.addCustomText(pt.x, pt.y, state);
        ////   }
        //// }
        cell = this.addCustomText(
          Number(pt.x - 70),
          Number(pt.y + 40),
          null,
          deviceInfo
        );
        //// mxGraph.prototype.dblClick.call(this, evt, cell);
        return cell;
      }
    };
    //// 绑定设备 text显示
    Graph.prototype.addCustomText = function (x, y, state, deviceInfo) {
      //// Creates a new edge label with a predefined text
      const label = new mxCell();
      label.value = deviceInfo.title + "：" + deviceInfo.value;
      label.style = "text;html=1;resizable=0;points=[];";
      label.infoType = deviceInfo.title;
      label.info = deviceInfo.value;
      label.geometry = new mxGeometry(0, 0, 0, 0);
      label.vertex = true;
      label.data_id = deviceInfo.id;
      //// label.visible = true;
      label.visible = deviceInfo.visible;

      if (state != null) {
        label.style +=
          "align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;";
        label.geometry.relative = true;
        label.connectable = false;

        //// Resets the relative location stored inside the geometry
        let pt2 = this.view.getRelativePoint(state, x, y);
        label.geometry.x = Math.round(pt2.x * 10000) / 10000;
        label.geometry.y = Math.round(pt2.y);

        //// Resets the offset inside the geometry to find the offset from the resulting point
        label.geometry.offset = new mxPoint(0, 0);
        pt2 = this.view.getPoint(state, label.geometry);

        const scale = this.view.scale;
        label.geometry.offset = new mxPoint(
          Math.round((x - pt2.x) / scale),
          Math.round((y - pt2.y) / scale)
        );
      } else {
        label.style += "autosize=1;align=left;verticalAlign=top;spacingTop=-4;";
        const tr = this.view.translate;
        label.geometry.width = 40;
        label.geometry.height = 20;
        label.geometry.x = Math.round(x / this.view.scale) - tr.x;
        label.geometry.y = Math.round(y / this.view.scale) - tr.y;
      }

      this.getModel().beginUpdate();
      try {
        this.addCells([label], state != null ? state.cell : null);
        this.fireEvent(new mxEventObject("textInserted", "cells", [label]));
        //// Updates size of text after possible change of style via event
        this.autoSizeCell(label);
      } finally {
        this.getModel().endUpdate();
      }

      //// console.log('devidelabel:', label);
      return label;
    };

    //// 绑定xml文件的text名称
    Graph.prototype.addTextNodeXml = function (evt, cell, xmlInfo) {
      //// console.log('绑定xml文件cell:', cell);
      if (this.isEnabled()) {
        const pt = mxUtils.convertPoint(
          this.container,
          mxEvent.getClientX(evt) + xmlInfo.x,
          mxEvent.getClientY(evt) + xmlInfo.y
        );
        //// console.log('绑定xml文件pt:', pt, xmlInfo);
        ////   var state = (this.model.isEdge(cell)) ? this.view.getState(cell) : null;
        //// //// Automatically adds new child cells to edges on double click
        //// if (evt != null && !this.model.isVertex(cell)) {
        ////   var src = mxEvent.getSource(evt);

        ////   if ((this.firstClickState == state && this.firstClickSource == src) &&
        ////     (state == null || (state.text == null || state.text.node == null ||
        ////       state.text.boundingBox == null || (!mxUtils.contains(state.text.boundingBox,
        ////         pt.x, pt.y) && !mxUtils.isAncestorNode(state.text.node, mxEvent.getSource(evt))))) &&
        ////     ((state == null && !this.isCellLocked(this.getDefaultParent())) ||
        ////       (state != null && !this.isCellLocked(state.cell))) &&
        ////     (state != null || (mxClient.IS_VML && src == this.view.getCanvas()) ||
        ////       (mxClient.IS_SVG && src == this.view.getCanvas().ownerSVGElement))) {
        ////     cell = this.addCustomTextXml(pt.x, pt.y, state);
        ////   }
        //// }
        cell = this.addCustomTextXml(
          Number(pt.x - 70),
          Number(pt.y + 40),
          null,
          xmlInfo
        );
        //// mxGraph.prototype.dblClick.call(this, evt, cell);
        return cell;
      }
    };
    //// 绑定绑定xml文件数据 text显示
    Graph.prototype.addCustomTextXml = function (x, y, state, xmlInfo) {
      //// console.log("xmlInfo:", xmlInfo)
      //// Creates a new edge label with a predefined text
      const label = new mxCell();
      label.value = xmlInfo.title + "：" + xmlInfo.name;
      label.style = "text;html=1;resizable=0;points=[];";
      label.infoType = xmlInfo.title;
      label.info = xmlInfo.name;
      label.geometry = new mxGeometry(0, 0, 0, 0);
      label.vertex = true;
      label.data_id = xmlInfo.id;
      //// label.visible = true;
      label.visible = xmlInfo.visible;

      if (state != null) {
        label.style +=
          "align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;";
        label.geometry.relative = true;
        label.connectable = false;

        //// Resets the relative location stored inside the geometry
        let pt2 = this.view.getRelativePoint(state, x, y);
        label.geometry.x = Math.round(pt2.x * 10000) / 10000;
        label.geometry.y = Math.round(pt2.y);

        //// Resets the offset inside the geometry to find the offset from the resulting point
        label.geometry.offset = new mxPoint(0, 0);
        pt2 = this.view.getPoint(state, label.geometry);

        const scale = this.view.scale;
        label.geometry.offset = new mxPoint(
          Math.round((x - pt2.x) / scale),
          Math.round((y - pt2.y) / scale)
        );
      } else {
        label.style += "autosize=1;align=left;verticalAlign=top;spacingTop=-4;";
        const tr = this.view.translate;
        label.geometry.width = 40;
        label.geometry.height = 20;
        label.geometry.x = Math.round(x / this.view.scale) - tr.x;
        label.geometry.y = Math.round(y / this.view.scale) - tr.y;
      }

      this.getModel().beginUpdate();
      try {
        this.addCells([label], state != null ? state.cell : null);
        this.fireEvent(new mxEventObject("textInserted", "cells", [label]));
        //// Updates size of text after possible change of style via event
        this.autoSizeCell(label);
      } finally {
        this.getModel().endUpdate();
      }
      //// console.log('XMLlabel:', label);
      return label;
    };

    /**
     * Adds a handler for clicking on shapes with links. This replaces all links in labels.
     */
    Graph.prototype.addClickHandler = function (
      highlight,
      beforeClick,
      onClick
    ) {
      //// Replaces links in labels for consistent right-clicks
      const checkLinks = mxUtils.bind(this, function () {
        const links = this.container.getElementsByTagName("a");

        if (links != null) {
          for (let i = 0; i < links.length; i++) {
            const href = this.getAbsoluteUrl(links[i].getAttribute("href"));

            if (href != null) {
              links[i].setAttribute("rel", this.linkRelation);
              links[i].setAttribute("href", href);

              if (beforeClick != null) {
                mxEvent.addGestureListeners(links[i], null, null, beforeClick);
              }
            }
          }
        }
      });

      this.model.addListener(mxEvent.CHANGE, checkLinks);
      checkLinks();

      const cursor = this.container.style.cursor;
      const tol = this.getTolerance();
      const graph = this;

      const mouseListener = {
        currentState: null,
        currentLink: null,
        highlight:
          highlight != null && highlight != "" && highlight != mxConstants.NONE
            ? new mxCellHighlight(graph, highlight, 4)
            : null,
        startX: 0,
        startY: 0,
        scrollLeft: 0,
        scrollTop: 0,
        updateCurrentState: function (me) {
          let tmp = me.sourceState;
          //// Gets topmost intersecting cell with link
          if (tmp == null || graph.getLinkForCell(tmp.cell) == null) {
            const cell = graph.getCellAt(
              me.getGraphX(),
              me.getGraphY(),
              null,
              null,
              null,
              function (state, x, y) {
                return graph.getLinkForCell(state.cell) == null;
              }
            );
            tmp = graph.view.getState(cell);
          }
          if (tmp != this.currentState) {
            if (this.currentState != null) {
              this.clear();
            }
            this.currentState = tmp;
            if (this.currentState != null) {
              this.activate(this.currentState);
            }
          }
        },
        mouseDown: function (sender, me) {
          this.startX = me.getGraphX();
          this.startY = me.getGraphY();
          this.scrollLeft = graph.container.scrollLeft;
          this.scrollTop = graph.container.scrollTop;
          if (
            this.currentLink == null &&
            graph.container.style.overflow == "auto"
          ) {
            graph.container.style.cursor = "move";
          }
          this.updateCurrentState(me);
        },
        mouseMove: function (sender, me) {
          if (graph.isMouseDown) {
            if (this.currentLink != null) {
              const dx = Math.abs(this.startX - me.getGraphX());
              const dy = Math.abs(this.startY - me.getGraphY());
              if (dx > tol || dy > tol) {
                this.clear();
              }
            }
          } else {
            //// Checks for parent link
            let linkNode = me.getSource();
            while (linkNode != null && linkNode.nodeName.toLowerCase() != "a") {
              linkNode = linkNode.parentNode;
            }
            if (linkNode != null) {
              this.clear();
            } else {
              if (
                graph.tooltipHandler != null &&
                this.currentLink != null &&
                this.currentState != null
              ) {
                graph.tooltipHandler.reset(me, true, this.currentState);
              }

              if (
                this.currentState != null &&
                (me.getState() == this.currentState ||
                  me.sourceState == null) &&
                graph.intersects(
                  this.currentState,
                  me.getGraphX(),
                  me.getGraphY()
                )
              ) {
                return;
              }
              this.updateCurrentState(me);
            }
          }
        },
        mouseUp: function (sender, me) {
          //// todo mouseup
          const source = me.getSource();
          const evt = me.getEvent();
          //// Checks for parent link
          let linkNode = source;
          while (linkNode != null && linkNode.nodeName.toLowerCase() != "a") {
            linkNode = linkNode.parentNode;
          }
          //// Ignores clicks on links and collapse/expand icon
          if (
            linkNode == null &&
            Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
            Math.abs(this.scrollTop - graph.container.scrollTop) < tol &&
            (me.sourceState == null ||
              !me.isSource(me.sourceState.control)) &&
            (((mxEvent.isLeftMouseButton(evt) ||
              mxEvent.isMiddleMouseButton(evt)) &&
              !mxEvent.isPopupTrigger(evt)) ||
              mxEvent.isTouchEvent(evt))
          ) {
            if (this.currentLink != null) {
              const blank = graph.isBlankLink(this.currentLink);

              if (
                (this.currentLink.substring(0, 5) === "data:" || !blank) &&
                beforeClick != null
              ) {
                beforeClick(evt, this.currentLink);
              }

              if (!mxEvent.isConsumed(evt)) {
                const target = mxEvent.isMiddleMouseButton(evt)
                  ? "_self"
                  : blank
                    ? graph.linkTarget
                    : "_top";
                graph.openLink(this.currentLink, target);
                me.consume();
              }
            } else if (
              onClick != null &&
              !me.isConsumed() &&
              Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
              Math.abs(this.scrollTop - graph.container.scrollTop) < tol &&
              Math.abs(this.startX - me.getGraphX()) < tol &&
              Math.abs(this.startY - me.getGraphY()) < tol
            ) {
              onClick(me.getEvent());
            }
          }
          this.clear();
        },
        activate: function (state) {
          this.currentLink = graph.getAbsoluteUrl(
            graph.getLinkForCell(state.cell)
          );
          if (this.currentLink != null) {
            graph.container.style.cursor = "pointer";

            if (this.highlight != null) {
              this.highlight.highlight(state);
            }
          }
        },
        clear: function () {
          if (graph.container != null) {
            graph.container.style.cursor = cursor;
          }
          this.currentState = null;
          this.currentLink = null;

          if (this.highlight != null) {
            this.highlight.hide();
          }

          if (graph.tooltipHandler != null) {
            graph.tooltipHandler.hide();
          }
        }
      };

      //// Ignores built-in click handling
      graph.click = function (me) {
        console.log(me);
      };
      graph.addMouseListener(mouseListener);
      mxEvent.addListener(document, "mouseleave", function (evt) {
        mouseListener.clear();
      });
    };

    /**
     * Duplicates the given cells and returns the duplicates.
     */
    Graph.prototype.duplicateCells = function (cells, append) {
      cells = cells != null ? cells : this.getSelectionCells();
      append = append != null ? append : true;
      cells = this.model.getTopmostCells(cells);
      const model = this.getModel();
      const s = this.gridSize;
      const select = [];

      model.beginUpdate();
      try {
        const clones = this.cloneCells(cells, false, null, true);
        for (let i = 0; i < cells.length; i++) {
          const parent = model.getParent(cells[i]);
          const child = this.moveCells([clones[i]], s, s, false)[0];
          select.push(child);
          if (append) {
            model.add(parent, clones[i]);
          } else {
            //// Maintains child index by inserting after clone in parent
            const index = parent.getIndex(cells[i]);
            model.add(parent, clones[i], index + 1);
          }
        }
      } finally {
        model.endUpdate();
      }
      return select;
    };

    /**
     * Inserts the given image at the cursor in a content editable text box using
     * the insertimage command on the document instance.
     */
    Graph.prototype.insertImage = function (newValue, w, h) {
      //// To find the new image, we create a list of all existing links first
      if (newValue != null && this.cellEditor.textarea != null) {
        const tmp = this.cellEditor.textarea.getElementsByTagName("img");
        const oldImages = [];

        for (var i = 0; i < tmp.length; i++) {
          oldImages.push(tmp[i]);
        }

        //// LATER: Fix inserting link/image in IE8/quirks after focus lost
        document.execCommand("insertimage", false, newValue);

        //// Sets size of new image
        const newImages = this.cellEditor.textarea.getElementsByTagName("img");

        if (newImages.length == oldImages.length + 1) {
          //// Inverse order in favor of appended images
          for (var i = newImages.length - 1; i >= 0; i--) {
            if (i == 0 || newImages[i] != oldImages[i - 1]) {
              //// Workaround for lost styles during undo and redo is using attributes
              newImages[i].setAttribute("width", w);
              newImages[i].setAttribute("height", h);
              break;
            }
          }
        }
      }
    };

    /**
     * Inserts the given image at the cursor in a content editable text box using
     * the insertimage command on the document instance.
     */
    Graph.prototype.insertLink = function (value) {
      if (this.cellEditor.textarea != null) {
        if (value.length == 0) {
          document.execCommand("unlink", false);
        } else if (mxClient.IS_FF) {
          //// Workaround for Firefox that adds a new link and removes
          //// the href from the inner link if its parent is a span is
          //// to remove all inner links inside the new outer link
          var tmp = this.cellEditor.textarea.getElementsByTagName("a");
          const oldLinks = [];

          for (var i = 0; i < tmp.length; i++) {
            oldLinks.push(tmp[i]);
          }

          document.execCommand("createlink", false, mxUtils.trim(value));

          //// Finds the new link element
          const newLinks = this.cellEditor.textarea.getElementsByTagName("a");

          if (newLinks.length == oldLinks.length + 1) {
            //// Inverse order in favor of appended links
            for (var i = newLinks.length - 1; i >= 0; i--) {
              if (newLinks[i] != oldLinks[i - 1]) {
                //// Removes all inner links from the new link and
                //// moves the children to the inner link parent
                var tmp = newLinks[i].getElementsByTagName("a");

                while (tmp.length > 0) {
                  const parent = tmp[0].parentNode;

                  while (tmp[0].firstChild != null) {
                    parent.insertBefore(tmp[0].firstChild, tmp[0]);
                  }

                  parent.removeChild(tmp[0]);
                }

                break;
              }
            }
          }
        } else {
          //// LATER: Fix inserting link/image in IE8/quirks after focus lost
          document.execCommand("createlink", false, mxUtils.trim(value));
        }
      }
    };

    /**
     *
     * @param cell
     * @returns {Boolean}
     */
    Graph.prototype.isCellResizable = function (cell) {
      const result = mxGraph.prototype.isCellResizable.apply(this, arguments);

      const state = this.view.getState(cell);
      const style = state != null ? state.style : this.getCellStyle(cell);

      return (
        result ||
        (mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, "1") != "0" &&
          style[mxConstants.STYLE_WHITE_SPACE] == "wrap")
      );
    };

    /**
     * Function: distributeCells
     *
     * Distribuets the centers of the given cells equally along the available
     * horizontal or vertical space.
     *
     * Parameters:
     *
     * horizontal - Boolean that specifies the direction of the distribution.
     * cells - Optional array of <mxCells> to be distributed. Edges are ignored.
     */
    Graph.prototype.distributeCells = function (horizontal, cells) {
      if (cells == null) {
        cells = this.getSelectionCells();
      }

      if (cells != null && cells.length > 1) {
        const vertices = [];
        let max = null;
        let min = null;

        for (var i = 0; i < cells.length; i++) {
          if (this.getModel().isVertex(cells[i])) {
            const state = this.view.getState(cells[i]);

            if (state != null) {
              const tmp = horizontal ? state.getCenterX() : state.getCenterY();
              max = max != null ? Math.max(max, tmp) : tmp;
              min = min != null ? Math.min(min, tmp) : tmp;

              vertices.push(state);
            }
          }
        }

        if (vertices.length > 2) {
          vertices.sort(function (a, b) {
            return horizontal ? a.x - b.x : a.y - b.y;
          });

          const t = this.view.translate;
          const s = this.view.scale;

          min = min / s - (horizontal ? t.x : t.y);
          max = max / s - (horizontal ? t.x : t.y);

          this.getModel().beginUpdate();
          try {
            const dt = (max - min) / (vertices.length - 1);
            let t0 = min;

            for (var i = 1; i < vertices.length - 1; i++) {
              const pstate = this.view.getState(
                this.model.getParent(vertices[i].cell)
              );
              let geo = this.getCellGeometry(vertices[i].cell);
              t0 += dt;

              if (geo != null && pstate != null) {
                geo = geo.clone();

                if (horizontal) {
                  geo.x = Math.round(t0 - geo.width / 2) - pstate.origin.x;
                } else {
                  geo.y = Math.round(t0 - geo.height / 2) - pstate.origin.y;
                }

                this.getModel().setGeometry(vertices[i].cell, geo);
              }
            }
          } finally {
            this.getModel().endUpdate();
          }
        }
      }

      return cells;
    };

    /**
     * Adds meta-drag an Mac.
     * @param evt
     * @returns {boolean}
     */
    Graph.prototype.isCloneEvent = function (evt) {
      return (
        (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) ||
        mxEvent.isControlDown(evt)
      );
    };

    /**
     * encodeCells
     *
     * @param cells
     * @returns {*}
     */
    Graph.prototype.encodeCells = function (cells) {
      const clones = this.cloneCells(cells);

      //// Creates a dictionary for fast lookups
      const dict = new mxDictionary();

      for (var i = 0; i < cells.length; i++) {
        dict.put(cells[i], true);
      }

      //// Checks for orphaned relative children and makes absolute
      for (var i = 0; i < clones.length; i++) {
        const state = this.view.getState(cells[i]);

        if (state != null) {
          const geo = this.getCellGeometry(clones[i]);

          if (
            geo != null &&
            geo.relative &&
            !this.model.isEdge(cells[i]) &&
            !dict.get(this.model.getParent(cells[i]))
          ) {
            geo.relative = false;
            geo.x = state.x / state.view.scale - state.view.translate.x;
            geo.y = state.y / state.view.scale - state.view.translate.y;
          }
        }
      }

      const codec = new mxCodec();
      const model = new mxGraphModel();
      const parent = model.getChildAt(model.getRoot(), 0);

      for (var i = 0; i < cells.length; i++) {
        model.add(parent, clones[i]);
      }
      return codec.encode(model);
    };

    /**
     * Translates this point by the given vector.
     *
     * @param {number} dx X-coordinate of the translation.
     * @param {number} dy Y-coordinate of the translation.
     */
    Graph.prototype.createSvgImageExport = function () {
      const exp = new mxImageExport();

      //// Adds hyperlinks (experimental)
      exp.getLinkForCellState = mxUtils.bind(this, function (state, canvas) {
        return this.getLinkForCell(state.cell);
      });

      return exp;
    };

    /**
     * Translates this point by the given vector.
     *
     * @param {number} dx X-coordinate of the translation.
     * @param {number} dy Y-coordinate of the translation.
     */
    Graph.prototype.getSvg = function (
      background,
      scale,
      border,
      nocrop,
      crisp,
      ignoreSelection,
      showText,
      imgExport,
      linkTarget,
      hasShadow
    ) {
      //// Disable Css Transforms if it is used
      const origUseCssTrans = this.useCssTransforms;

      if (origUseCssTrans) {
        this.useCssTransforms = false;
        this.view.revalidate();
        this.sizeDidChange();
      }

      try {
        scale = scale != null ? scale : 1;
        border = border != null ? border : 0;
        crisp = crisp != null ? crisp : true;
        ignoreSelection = ignoreSelection != null ? ignoreSelection : true;
        showText = showText != null ? showText : true;

        const bounds =
          ignoreSelection || nocrop
            ? this.getGraphBounds()
            : this.getBoundingBox(this.getSelectionCells());

        if (bounds == null) {
          throw Error(mxResources.get("drawingEmpty"));
        }

        const vs = this.view.scale;

        //// Prepares SVG document that holds the output
        const svgDoc = mxUtils.createXmlDocument();
        const root =
          svgDoc.createElementNS != null
            ? svgDoc.createElementNS(mxConstants.NS_SVG, "svg")
            : svgDoc.createElement("svg");

        if (background != null) {
          if (root.style != null) {
            root.style.backgroundColor = background;
          } else {
            root.setAttribute("style", "background-color:" + background);
          }
        }

        if (svgDoc.createElementNS == null) {
          root.setAttribute("xmlns", mxConstants.NS_SVG);
          root.setAttribute("xmlns:xlink", mxConstants.NS_XLINK);
        } else {
          //// KNOWN: Ignored in IE9-11, adds namespace for each image element instead. No workaround.
          root.setAttributeNS(
            "http:////www.w3.org/2000/xmlns/",
            "xmlns:xlink",
            mxConstants.NS_XLINK
          );
        }

        const s = scale / vs;
        const w =
          Math.max(1, Math.ceil(bounds.width * s) + 2 * border) +
          (hasShadow ? 5 : 0);
        const h =
          Math.max(1, Math.ceil(bounds.height * s) + 2 * border) +
          (hasShadow ? 5 : 0);

        root.setAttribute("version", "1.1");
        root.setAttribute("width", w + "px");
        root.setAttribute("height", h + "px");
        root.setAttribute(
          "viewBox",
          (crisp ? "-0.5 -0.5" : "0 0") + " " + w + " " + h
        );
        svgDoc.appendChild(root);

        //// Renders graph. Offset will be multiplied with state's scale when painting state.
        //// TextOffset only seems to affect FF output but used everywhere for consistency.
        const group =
          svgDoc.createElementNS != null
            ? svgDoc.createElementNS(mxConstants.NS_SVG, "g")
            : svgDoc.createElement("g");
        root.appendChild(group);

        const svgCanvas = this.createSvgCanvas(group);
        svgCanvas.foOffset = crisp ? -0.5 : 0;
        svgCanvas.textOffset = crisp ? -0.5 : 0;
        svgCanvas.imageOffset = crisp ? -0.5 : 0;
        svgCanvas.translate(
          Math.floor((border / scale - bounds.x) / vs),
          Math.floor((border / scale - bounds.y) / vs)
        );

        //// Convert HTML entities
        const htmlConverter = document.createElement("textarea");

        //// Adds simple text fallback for viewers with no support for foreignObjects
        const createAlternateContent = svgCanvas.createAlternateContent;
        svgCanvas.createAlternateContent = function (
          fo,
          x,
          y,
          w,
          h,
          str,
          align,
          valign,
          wrap,
          format,
          overflow,
          clip,
          rotation
        ) {
          const s = this.state;

          //// Assumes a max character width of 0.2em
          if (
            this.foAltText != null &&
            (w == 0 || (s.fontSize != 0 && str.length < (w * 5) / s.fontSize))
          ) {
            const alt = this.createElement("text");
            alt.setAttribute("x", Math.round(w / 2));
            alt.setAttribute("y", Math.round((h + s.fontSize) / 2));
            alt.setAttribute("fill", s.fontColor || "black");
            alt.setAttribute("text-anchor", "middle");
            alt.setAttribute("font-size", Math.round(s.fontSize) + "px");
            alt.setAttribute("font-family", s.fontFamily);

            if (
              (s.fontStyle & mxConstants.FONT_BOLD) ==
              mxConstants.FONT_BOLD
            ) {
              alt.setAttribute("font-weight", "bold");
            }

            if (
              (s.fontStyle & mxConstants.FONT_ITALIC) ==
              mxConstants.FONT_ITALIC
            ) {
              alt.setAttribute("font-style", "italic");
            }

            if (
              (s.fontStyle & mxConstants.FONT_UNDERLINE) ==
              mxConstants.FONT_UNDERLINE
            ) {
              alt.setAttribute("text-decoration", "underline");
            }

            try {
              htmlConverter.innerHTML = str;
              alt.textContent = htmlConverter.value;

              return alt;
            } catch (e) {
              return createAlternateContent.apply(this, arguments);
            }
          } else {
            return createAlternateContent.apply(this, arguments);
          }
        };

        //// Paints background image
        const bgImg = this.backgroundImage;

        if (bgImg != null) {
          const s2 = vs / scale;
          const tr = this.view.translate;
          const tmp = new mxRectangle(
            tr.x * s2,
            tr.y * s2,
            bgImg.width * s2,
            bgImg.height * s2
          );

          //// Checks if visible
          if (mxUtils.intersects(bounds, tmp)) {
            svgCanvas.image(
              tr.x,
              tr.y,
              bgImg.width,
              bgImg.height,
              bgImg.src,
              true
            );
          }
        }

        svgCanvas.scale(s);
        svgCanvas.textEnabled = showText;

        imgExport = imgExport != null ? imgExport : this.createSvgImageExport();
        const imgExportDrawCellState = imgExport.drawCellState;

        //// Ignores custom links
        const imgExportGetLinkForCellState = imgExport.getLinkForCellState;

        imgExport.getLinkForCellState = function (state, canvas) {
          const result = imgExportGetLinkForCellState.apply(this, arguments);

          return result != null && !state.view.graph.isCustomLink(result)
            ? result
            : null;
        };

        //// Implements ignoreSelection flag
        imgExport.drawCellState = function (state, canvas) {
          const graph = state.view.graph;
          let selected = graph.isCellSelected(state.cell);
          let parent = graph.model.getParent(state.cell);

          //// Checks if parent cell is selected
          while (!ignoreSelection && !selected && parent != null) {
            selected = graph.isCellSelected(parent);
            parent = graph.model.getParent(parent);
          }

          if (ignoreSelection || selected) {
            imgExportDrawCellState.apply(this, arguments);
          }
        };

        imgExport.drawState(
          this.getView().getState(this.model.root),
          svgCanvas
        );
        this.updateSvgLinks(root, linkTarget, true);

        return root;
      } finally {
        if (origUseCssTrans) {
          this.useCssTransforms = true;
          this.view.revalidate();
          this.sizeDidChange();
        }
      }
    };

    /**
     * Hook for creating the canvas used in getSvg.
     */
    Graph.prototype.updateSvgLinks = function (node, target, removeCustom) {
      const links = node.getElementsByTagName("a");

      for (let i = 0; i < links.length; i++) {
        let href = links[i].getAttribute("href");

        if (href == null) {
          href = links[i].getAttribute("xlink:href");
        }

        if (href != null) {
          if (target != null && /^https?:\/\////.test(href)) {
            links[i].setAttribute("target", target);
          } else if (removeCustom && this.isCustomLink(href)) {
            links[i].setAttribute("href", "javascript:void(0);");
          }
        }
      }
    };

    /**
     * Hook for creating the canvas used in getSvg.
     */
    Graph.prototype.createSvgCanvas = function (node) {
      return new mxSvgCanvas2D(node);
    };

    /**
     * Returns the first ancestor of the current selection with the given name.
     */
    Graph.prototype.getSelectedElement = function () {
      let node = null;

      if (window.getSelection) {
        const sel = window.getSelection();

        if (sel.getRangeAt && sel.rangeCount) {
          const range = sel.getRangeAt(0);
          node = range.commonAncestorContainer;
        }
      } else if (document.selection) {
        node = document.selection.createRange().parentElement();
      }

      return node;
    };

    /**
     * Returns the first ancestor of the current selection with the given name.
     */
    Graph.prototype.getParentByName = function (node, name, stopAt) {
      while (node != null) {
        if (node.nodeName == name) {
          return node;
        }

        if (node == stopAt) {
          return null;
        }

        node = node.parentNode;
      }

      return node;
    };

    /**
     * Returns the first ancestor of the current selection with the given name.
     */
    Graph.prototype.getParentByNames = function (node, names, stopAt) {
      while (node != null) {
        if (mxUtils.indexOf(names, node.nodeName) >= 0) {
          return node;
        }

        if (node == stopAt) {
          return null;
        }

        node = node.parentNode;
      }

      return node;
    };

    /**
     * Selects the given node.
     */
    Graph.prototype.selectNode = function (node) {
      let sel = null;

      //// IE9 and non-IE
      if (window.getSelection) {
        sel = window.getSelection();

        if (sel.getRangeAt && sel.rangeCount) {
          var range = document.createRange();
          range.selectNode(node);
          sel.removeAllRanges();
          sel.addRange(range);
        }
      }
      //// IE < 9
      else if ((sel = document.selection) && sel.type != "Control") {
        const originalRange = sel.createRange();
        originalRange.collapse(true);
        var range = sel.createRange();
        range.setEndPoint("StartToStart", originalRange);
        range.select();
      }
    };

    /**
     * Inserts a new row into the given table.
     */
    Graph.prototype.insertRow = function (table, index) {
      const bd = table.tBodies[0];
      const cells = bd.rows[0].cells;
      let cols = 0;

      //// Counts columns including colspans
      for (var i = 0; i < cells.length; i++) {
        const colspan = cells[i].getAttribute("colspan");
        cols += colspan != null ? parseInt(colspan) : 1;
      }

      const row = bd.insertRow(index);

      for (var i = 0; i < cols; i++) {
        mxUtils.br(row.insertCell(-1));
      }

      return row.cells[0];
    };

    /**
     * Deletes the given column.
     */
    Graph.prototype.deleteRow = function (table, index) {
      table.tBodies[0].deleteRow(index);
    };

    /**
     * Deletes the given column.
     */
    Graph.prototype.insertColumn = function (table, index) {
      const hd = table.tHead;

      if (hd != null) {
        //// TODO: use colIndex
        for (let h = 0; h < hd.rows.length; h++) {
          const th = document.createElement("th");
          hd.rows[h].appendChild(th);
          mxUtils.br(th);
        }
      }

      const bd = table.tBodies[0];

      for (let i = 0; i < bd.rows.length; i++) {
        const cell = bd.rows[i].insertCell(index);
        mxUtils.br(cell);
      }

      return bd.rows[0].cells[index >= 0 ? index : bd.rows[0].cells.length - 1];
    };

    /**
     * Deletes the given column.
     */
    Graph.prototype.deleteColumn = function (table, index) {
      if (index >= 0) {
        const bd = table.tBodies[0];
        const rows = bd.rows;

        for (let i = 0; i < rows.length; i++) {
          if (rows[i].cells.length > index) {
            rows[i].deleteCell(index);
          }
        }
      }
    };

    /**
     * Inserts the given HTML at the caret position (no undo).
     */
    Graph.prototype.pasteHtmlAtCaret = function (html) {
      let sel, range;

      //// IE9 and non-IE
      if (window.getSelection) {
        sel = window.getSelection();

        if (sel.getRangeAt && sel.rangeCount) {
          range = sel.getRangeAt(0);
          range.deleteContents();

          //// Range.createContextualFragment() would be useful here but is
          //// only relatively recently standardized and is not supported in
          //// some browsers (IE9, for one)
          const el = document.createElement("div");
          el.innerHTML = html;
          let frag = document.createDocumentFragment(),
            node;

          while ((node = el.firstChild)) {
            const lastNode = frag.appendChild(node);
          }

          range.insertNode(frag);
        }
      }
      //// IE < 9
      else if ((sel = document.selection) && sel.type != "Control") {
        //// FIXME: Does not work if selection is empty
        sel.createRange().pasteHTML(html);
      }
    };

    /**
     * Creates an anchor elements for handling the given link in the
     * hint that is shown when the cell is selected.
     */
    Graph.prototype.createLinkForHint = function (link, label) {
      link = link != null ? link : "javascript:void(0);";

      if (label == null || label.length == 0) {
        if (this.isCustomLink(link)) {
          label = this.getLinkTitle(link);
        } else {
          label = link;
        }
      }

      //// Helper function to shorten strings
      function short(str, max) {
        if (str.length > max) {
          str =
            str.substring(0, Math.round(max / 2)) +
            "..." +
            str.substring(str.length - Math.round(max / 4));
        }

        return str;
      }

      const a = document.createElement("a");
      a.setAttribute("rel", this.linkRelation);
      a.setAttribute("href", this.getAbsoluteUrl(link));
      a.setAttribute(
        "title",
        short(this.isCustomLink(link) ? this.getLinkTitle(link) : link, 80)
      );

      if (this.linkTarget != null) {
        a.setAttribute("target", this.linkTarget);
      }

      //// Adds shortened label to link
      mxUtils.write(a, short(label, 40));

      //// Handles custom links
      if (this.isCustomLink(link)) {
        mxEvent.addListener(
          a,
          "click",
          mxUtils.bind(this, function (evt) {
            this.customLinkClicked(link);
            mxEvent.consume(evt);
          })
        );
      }

      return a;
    };

    /**
     * Customized graph for touch devices.
     */
    Graph.prototype.initTouch = function () {
      //// Disables new connections via "hotspot"
      this.connectionHandler.marker.isEnabled = function () {
        return this.graph.connectionHandler.first != null;
      };

      //// Hides menu when editing starts
      this.addListener(mxEvent.START_EDITING, function (sender, evt) {
        this.popupMenuHandler.hideMenu();
      });

      //// Adds custom hit detection if native hit detection found no cell
      const graphUpdateMouseEvent = this.updateMouseEvent;
      this.updateMouseEvent = function (me) {
        me = graphUpdateMouseEvent.apply(this, arguments);

        if (mxEvent.isTouchEvent(me.getEvent()) && me.getState() == null) {
          let cell = this.getCellAt(me.graphX, me.graphY);

          if (
            cell != null &&
            this.isSwimlane(cell) &&
            this.hitsSwimlaneContent(cell, me.graphX, me.graphY)
          ) {
            cell = null;
          } else {
            me.state = this.view.getState(cell);

            if (me.state != null && me.state.shape != null) {
              this.container.style.cursor = me.state.shape.node.style.cursor;
            }
          }
        }

        if (me.getState() == null && this.isEnabled()) {
          this.container.style.cursor = "default";
        }

        return me;
      };

      //// Context menu trigger implementation depending on current selection state
      //// combined with support for normal popup trigger.
      let cellSelected = false;
      let selectionEmpty = false;
      let menuShowing = false;

      const oldFireMouseEvent = this.fireMouseEvent;

      this.fireMouseEvent = function (evtName, me, sender) {
        if (evtName == mxEvent.MOUSE_DOWN) {
          //// For hit detection on edges
          me = this.updateMouseEvent(me);

          cellSelected = this.isCellSelected(me.getCell());
          selectionEmpty = this.isSelectionEmpty();
          menuShowing = this.popupMenuHandler.isMenuShowing();
        }

        oldFireMouseEvent.apply(this, arguments);
      };

      //// Shows popup menu if cell was selected or selection was empty and background was clicked
      //// FIXME: Conflicts with mxPopupMenuHandler.prototype.getCellForPopupEvent in Editor.js by
      //// selecting parent for selected children in groups before this check can be made.
      this.popupMenuHandler.mouseUp = mxUtils.bind(this, function (sender, me) {
        this.popupMenuHandler.popupTrigger =
          !this.isEditing() &&
          this.isEnabled() &&
          (me.getState() == null || !me.isSource(me.getState().control)) &&
          (this.popupMenuHandler.popupTrigger ||
            (!menuShowing &&
              !mxEvent.isMouseEvent(me.getEvent()) &&
              ((selectionEmpty &&
                me.getCell() == null &&
                this.isSelectionEmpty()) ||
                (cellSelected && this.isCellSelected(me.getCell())))));
        mxPopupMenuHandler.prototype.mouseUp.apply(
          this.popupMenuHandler,
          arguments
        );
      });
    };

    /**
     * Handling of special nl2Br style for not converting newlines to breaks in HTML labels.
     * NOTE: Since it's easier to set this when the label is created we assume that it does
     * not change during the lifetime of the mxText instance.
     */
    const mxCellRendererInitializeLabel =
      mxCellRenderer.prototype.initializeLabel;
    mxCellRenderer.prototype.initializeLabel = function (state) {
      if (state.text != null) {
        state.text.replaceLinefeeds =
          mxUtils.getValue(state.style, "nl2Br", "1") != "0";
      }

      mxCellRendererInitializeLabel.apply(this, arguments);
    };

    const mxConstraintHandlerUpdate = mxConstraintHandler.prototype.update;
    mxConstraintHandler.prototype.update = function (me, source) {
      if (this.isKeepFocusEvent(me) || !mxEvent.isAltDown(me.getEvent())) {
        mxConstraintHandlerUpdate.apply(this, arguments);
      } else {
        this.reset();
      }
    };

    /**
     * No dashed shapes.
     */
    mxGuide.prototype.createGuideShape = function (horizontal) {
      const guide = new mxPolyline(
        [],
        mxConstants.GUIDE_COLOR,
        mxConstants.GUIDE_STROKEWIDTH
      );

      return guide;
    };

    const mainHandle = Graph.createSvgImage(
      18,
      18,
      '<rect x="9" y="9" width="9" height="9" stroke="#fff" fill="' +
      '#000' +
      '" stroke-width="1"/>'
    );

    mxOutline.prototype.sizerImage = mainHandle;

    //// Enables connections along the outline, virtual waypoints, parent highlight etc
    //// mxConnectionHandler.prototype.outlineConnect = true;
    mxCellHighlight.prototype.keepOnTop = true;

    mxPanningHandler.prototype.isPanningTrigger = function (me) {
      const evt = me.getEvent();

      return (
        (mxEvent.isLeftMouseButton(evt) &&
          ((this.useLeftButtonForPanning && me.getState() == null) ||
            (mxEvent.isControlDown(evt) && !mxEvent.isShiftDown(evt)))) ||
        (this.usePopupTrigger && mxEvent.isPopupTrigger(evt))
      );
    };

    //// Timer-based activation of outline connect in connection handler
    
  })();
}

export { Graph };

